UNIX進程控制(二)

進程標識

  每個進程都有一個非負整數表示的唯一進程ID。常將其用作其他標識符的一部分以保證其唯一性。例如:應用程序有時就把進程ID作爲名字的一部分來創建一個唯一的文件名。
  系統中有一些專用進程,但具體細節隨實現而不同。ID 0的進程通常是調度進程,常常被稱爲交換進程(swapper)。該進程是內核的一部分,他不執行任何磁盤上的程序,所以稱爲系統進程。進程ID 1通常是init進程…
  每個UNIX系統實現都有他自己的一套提供操作系統服務的內核進程,例如:某些UNIX的虛擬存儲器實現中,進程ID 2是頁守護進程,負責支持虛擬存儲器系統的分頁操作。
除了進程ID,每個進程還有一些其他的標識符,下列函數返回這些標識符。

include<unistd.h>
pid_t getpid(void);						//返回值:調用進程的進程ID
pid_t getppid(void);					//返回值:調用進程的父進程ID
uid_t getuid(void);						//返回值:調用進程的實際用戶ID
uid_t geteuid(void);					//返回值:調用進程的有效用戶ID
gid_t getgid(void);						//返回值:調用進程的實際組ID
gid_t getegid(void);					//返回值:調用進程的有效組ID

注意:這些函數都沒有出錯返回。

函數fork

#include<unistd.h>

pid_t fork(void);

功能:一個現有的進程可以通過fork函數創建新進程

返回值:子進程返回0,父進程返回子進程ID;若出錯,返回-1

注意:子進程是父進程的副本。子進程獲得父進程的數據空間、堆、棧、I/O流(共享文件指針和文件描述符)、緩衝區的拷貝等,與父進程共享代碼段

int main(void)  
{  
   int i=0;  
   for(i=0;i<3;i++){  
       pid_t fpid=fork();  
       if(fpid==0)  
           printf("son/n");  
       else  
           printf("father/n");  
   }  
   return 0;  
  
}  

這裏就不做詳細的解釋了,只做一個大概分析。
在這裏插入圖片描述
fork通常有以下兩種用法:
1、一個父進程希望複製自己,使父進程和子進程同時執行不同的代碼段。這在網絡服務進程中是常見的
——父進程等待客戶端的服務請求。當這種請求到達時,父進程調用fork,使子進程處理該請求。父進程則繼續等待下一個服務請求。
2、一個進程要執行一個不同的程序。這對shell是常見的情況。在這種情況下,子進程從fork返回後立即調用exec。

vfork函數

vfork函數與fork函數功能基本一致。
區別:
1、通過vfork創建的進程不復制父進程的地址空間(數據段、BSS段、堆、棧、I/O流(共享文件指針和文件描述符)、緩衝區),因爲子進程會立即調用exec。
2、vfork保證子進程先運行,在它調用exec或exit之後父進程才能被調度運行,當子進程調用這兩個函數中的任意一個時,父進程會恢復運行。

exec系列函數

int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg, ...,char * const envp[]);
int execve(const char *path, char *const argv[],char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int fexecve(int fd, char *const argv[],char *const envp[]);

這些函數之間的第一個區別是前四個函數取路徑名作爲參數,後兩個函數取文件名作爲參數,最後一個取文件描述符作爲參數。
在很多UNIX實現中,這7個函數只有execve是內核系統調用。另外六個是庫函數,他們最終都要調用該系統調用。這七個函數之間的關係如下圖
在這裏插入圖片描述
exec函數實例:

#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
char* env_init[] = {"USER=unknow","PATH=/temp",NULL};
int main()
{
	pid_t pid;
	if(pid=fork()<0)
	{
		err_sys("fork error");
	}
	else if(pid==0)
	{
		if(execle("/home/sar/bin/echoall","echoall","myarg1","MY ARG2",(char *)0,env_init)<0)
		{
			err_sys("execle error");
		}
		if(waitpid(pid,NULL,0)<0)
		{
			err_sys("wait error");
		}
		if(pid=fork()<0)
		{
			err_sys("fork error");
		}else if(pid==0)
		{
			if(execlp("echoall","echoall","only 1 arg",(char *)0)<0)
			{
				err_sys("execlp error");
			}
		}
	}
	exit(0);
}

函數wait和waitpid

當一個進程正常或異常終止時,內核就向其父親發送SIGCHLD信號。因爲子進程終止是異步事件,所以這種信號也是內核向父進程發的異步通知。

調用wait或waitpid函數的進程會發生什麼?
● 如果其所有進程都還在運行,則阻塞。
● 如果一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態立即返回。
●如果它沒有任何進程,則立即出錯返回。

pid_t wait(int *statloc);
功能:等待所有子進程結束 ,並獲取到最終的狀態碼。只要有一個進程結束就立即返回。

pid_t waitpid(pid_t pid,int *statloc,int options);

功能:等待指定的進程結束,並獲取到最終的狀態碼。

statloc是一個整形指針。如果statloc不是一個空指針,則終止進程的終止狀態就存放在它所指向的單元裏。如果不關心終止狀態,可將參數指定爲空指針。

POSIX.1規定,終止狀態用定義在<sys/wait.h>中的各個宏來查看,有4個互斥的宏可用來取得進程終止的原因,它們的名字都以WIF開始。

說明
WIFEXITED 若爲正常終止子進程返回的狀態,則爲真。對於這種情況,可執行WEXITSTATUS,獲取子進程傳送給exit或_exit參數的低8位
WIFSIGNALLED 若爲異常終止子進程返回的狀態,則爲真
WIFSTOPPED 若爲當前暫停子進程的返回的狀態,則爲真
WIFCONTINUED 若在作業控制暫停後已經繼續的子進程返回了狀態,則爲真

關於waitpid函數中pid參數的作用解釋

pid==-1 等待任一子進程,此種情況下,waitpid與wait等效
pid>0 等待進程ID與pid相等的子進程
pid==0 等待組ID等於調用進程組ID的任一子進程
pid<-1 等待組ID等於pid絕對值的任一子進程

options參數使我們能進一步控制waitpid的操作

常量 說明
WCONTINUED 由pid指定的子進程停止後已經繼續,但其狀態尚未報告,則返回其狀態
WNOHANG 若由pid指定的子進程並不是立即可用的,則waitpid不阻塞,此時返回0
WUNTRACED 由pid指定的子進程已停止,但其狀態尚未報告,則返回其狀態

這兩個函數的區別:
1、子一個子進程終止前,wait使其調用者阻塞,而waitpid有一個選項,可使調用者不阻塞。
2、waitpid並不等待在其調用之後的第一個終止子進程,他又若干個選項,可控制所等待的進程。

函數system

int system(const char *cmdstring)

功能:執行系統命令,也可以加載可執行程序。

因爲system在實現中調用了fork、exec、waitpid,所以有3種返回值
(1)、fork失敗或者waitpid返回除EINTR之外的出錯,則sysytem返回-1,並且設置errno以指示錯誤類型。
(2)、如果exec失敗,則其返回值如同shell執行了exit(127)一樣。
(3)、三個函數都成功,那麼返回值是shell的終止狀態。

進程終止

有8種方式使進程終止,其中5種爲正常終止,它們是:
(1)、從main函數返回;
(2)、調用exit函數;
(3)、調用_exit或_Exit;
(4)、最後一個線程從其啓動例程返回;
(5)、從最後一個線程調用pthread_exit;
異常終止有3種方式,它們是
(6)、調用abort;
(7)、接收到一個信號;
(8)、最後一個線程對取消請求做出響應。

void exit(int status);

功能:調用者立即結束該進程

status:退出狀態碼,可以在父進程中獲取到,子進程留給父進程的遺言

退出前會做的事情:
  1、先調用所有事先註冊的函數(通過 atexit/on_exit 註冊的)
    int atexit(void (*function)(void));
     功能:註冊一個函數,當進程通過exit函數結束時調用
     function:函數指針,無返回值無參數
     返回值:成功爲0,失敗-1
     int on_exit(void (*function)(int , void *), void *arg);
     功能:註冊一個函數,當進程通過exit函數開始結束時調用
     function:函數指針,無返回值,參數1爲exit函數的參數,參數2爲on_exit的第二個參數。
     arg:當function函數被調用是傳遞給它第二參數
   2、沖刷所有處在未關閉狀態的標準I/O流
   3、返回一個整數( 0 (EXIT_SUCCESS) / 1 (EXIT_FAILURE) )給操作系統
   4、該函數不會返回,它的功能實現藉助了_exit/_Exit
注意:atexit 和 on_exit 註冊的函數都會加入同一個棧結構中,誰最後登記的誰最先執行,他們的區別只有註冊的函數格式不同。

#include <unistd.h>

void _exit(int status);

#include <stdlib.h>

void _Exit(int status);//調用了系統的_exit(爲了兼容)

功能:調用的進程會結束,沒有返回值。

status:會被父進程獲取到(低八位,一個字節)。

說明:
   1、進程結束前會關閉所有處於打開狀態的文件描述符
   2、把所有的子進程託付給孤兒院init
   3、向它的父進程發送SIGCHLD信號。
注意:exit函數也會執行以上操作,因爲它底層調用了_exit/_Exit

進程時間

任一進程都可以調用times函數獲得它自己以及已終止子進程的上述值。

#include <sys/times.h>

clock_t times(struct tms *buf);

此函數填寫由buf指向的tms結構,該結構定義如下

struct tms {
               clock_t tms_utime;  /* user time */
               clock_t tms_stime;  /* system time */
               clock_t tms_cutime; /* user time of children */
               clock_t tms_cstime; /* system time of children */
           };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章