1. 程式人生 > >Unix環境高級編程(六)進程控制

Unix環境高級編程(六)進程控制

修改 退出狀態 註意 perror father cve pos d參數 system函數

本章介紹Unix的進程控制,包括進程創建,執行程序和進程終止,進程的屬性,exec函數系列,system函數,進程會計機制。

1、進程標識符

  每一個進程都有一個非負整數標識的唯一進程ID。ID為0表示調度進程,即交換進程,是內核的一部分,也稱為系統進程,不執行任何磁盤操作。ID為1的進程為init進程,init進程不會終止,他是一個普通的用戶進程,需要超級用戶特權運行。獲取標識符函數如下:

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);   //調用進程的進程ID
pid_t getppid(void); //調用進程的父進程ID


gid_t getgid(void); //調用進程的實際組ID
gid_t getegid(void); //調用進程的有效組ID
uid_t getuid(void); //調用進程的實際用戶ID
uid_t geteuid(void); //調用進程的有效用戶ID

2、fork函數

  一個現有的進程可以調用fork函數創建一個新進程。函數原型為:pid_t fork(void)。有fork創建的新進程稱為子進程,fork函數調用一次返回兩次。子進程的返回值為0,父進程的返回值為新子進程的ID。子進程和父進程舉行執行fork調用後的指令,子進程是父進程的副本。子進程獲得父進程的數據空間、棧和隊的副本,父子進程不共享這些存儲空間部分,共享正文段。子進程相當於父進程克隆了一份自己。 創建新進程成功後,系統中出現兩個基本完全相同的進程,這兩個進程執行沒有固定的先後順序,哪個進程先執行要看系統的進程調度策略。

  fork出錯可能有兩種原因:
1)當前的進程數已經達到了系統規定的上限,這時errno的值被設置為EAGAIN。
2)系統內存不足,這時errno的值被設置為ENOMEM。

註意的是:父進程設置的文件鎖不會被子進程繼承。

寫個程序,創建一個子進程,在子進程中改變變量的值,然後父子進程同時輸出變量的值,看看有什麽變化,同時輸出子進程的標識符信息。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <unistd.h>
 6 #include <errno.h>
 7 //全局變量
 8 int global = 100;
 9 char buf[]="a write to stdout.\n";
10 
11 int main()
12 {
13     int     var;
14     pid_t   pid;
15 
16     var = 90;
17     write(STDOUT_FILENO,buf,sizeof(buf)-1);
18     //創建一個子進程
19     pid = fork();
20     if(pid == -1)      //出錯
21     {
22         perror("fork() error");
23         exit(-1);
24     }
25     if(pid == 0)     //子進程
26     {
27         printf("This is child process.\n");
28         printf("Child process id is:%d\n",getpid());
29         printf("Father process id is:%d\n",getppid());
30         //子進程修改數據
31         global++;
32         var++;
33     }
34     if(pid > 0)   //父進程
35       sleep(3);  //等待子進程先執行
36     printf("pid = %d,ppid =%d,global=%d,var=%d\n",getpid(),getppid(),global,var);
37     return 0;
38 }
程序執行結果如下:

技術分享圖片

從結果可以看出子進程擁有自己的數據空間,不與父進程共享數據空間。

  vfork函數的調用序列和返回值與fork相同,但是vfork並不將父進程的地址空間完全復制到子進程中,在調用exec和exit之前在父進程的空間中運行。vfork保證子進程先運行,在它調用exec或exit之後父進程才可能被調度運行。

  進程終止最後都會執行內核中的同一段代碼,為相應的進程關閉所有打開描述符,釋放它所使用的存儲器等。子進程可以通過exit函數通知父進程是如何終止的,父進程調用wait或waitpid函數可以獲取終止狀態。子進程是在父進程調用fork後產生的,如果父進程在子進程之前終止,則將子進程的父進程改變為init進程,保證每一個進程都有一個父進程。一個已經終止,但其父進程尚沒有對其進行善後處理的進程稱為僵死進程(zombie)。由init進程領養的子進程不會變成僵死進程,因為init進程在子進程終止的時候會調用一個wait函數取得子進程的終止狀態。

  當一個進程正常或者異常終止的時,內核就像其父進程發送SIGCHLD信號。調用wait和waitpid函數可能發生的情況:(1)如果所有子進程都還在運行,則阻塞;(2)如果一個子進程已經終止,正等待父進程獲取進程終止狀態,則取得孩子的終止狀態立刻返回;(3)若果沒有任何子進程,則立即出錯返回。如果在任意時刻調用wait,則進程可能會阻塞。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status); //在一個子進程終止前,wait使調用者阻塞

pid_t waitpid(pid_t pid, int *status, int options); //可以使調用者不阻塞

#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>
pid_t wait3(int *
status, int options,struct rusage *rusage);
pid_t wait4(pid_t
pid, int *status, int options, struct rusage *rusage);
寫個程序,子進程給出退出狀態,父進程通過wait和waitpid獲取退出狀態。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/wait.h>
 5 #include <sys/types.h>
 6 #include <signal.h>
 7 
 8 int main()
 9 {
10     pid_t pid;
11     int status;
12     if((pid = fork()) == -1)
13     {
14         perror("fork() error");
15         exit(-1);
16     }
17     if(pid == 0)
18       exit(0);
19     if(wait(&status) == pid)
20             printf("child normal exit,exit status=%d\n",WEXITSTATUS(status));
21     if((pid = fork()) == -1)
22     {
23         perror("fork() error");
24         exit(-1);
25     }
26     if(pid == 0)
27         abort();
28     if(waitpid(pid,&status,0) == pid)
29             printf("child abnormal termination,signal number=%d\n",WTERMSIG(status));
30 }

程序執行結果如下:

技術分享圖片

waitpid函數中的pid參數取值情況:
pid=-1  等待任一子進程,此時相當於wait
pid>0 等待期進程ID與pid相等的子進程
pid==0 等待期組ID等於調用進程組ID的任一個子進程
pid<-1 等待其組ID等於pid絕對值的任一子進程

另外提供了一種避免僵死進程的方法:調用fork兩次。

  exec函數,用fork函數創建子進程後,子進程往往要調用一種exec函數以執行另外一個程序。當調用exec函數時,該進程執行的程序完全替換為新進程,exec函數不新建進程,只是用一個全新的程序替換了當前的正文、數據、堆和棧段。執行完之後,進程ID不會改變。在進程間通信的時候,經常需要調用exec函數啟動另外一個例程。函數原型如下:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[],char *const envp[]);

其中l表示列表(list),v標識矢量(vector)。execl、execlp、execle中每個命令行參數都是一個單獨參數,這種參數以空指針結尾。execv、execvp、execve命令行參數是一個指針數組。e標識環境變量,傳遞參數。寫個程序進行測試,程序分為兩部分,exec調用的程序,exec執行程序。程序如下:

exec調用程序如下,可執行文件名稱為exectest,存放在/home/anker/Programs目錄下。

技術分享圖片
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 int main(int argc,char *argv[])
 5 {
 6     int         i;
 7     char        **ptr;
 8     extern char **environ;
 9 
10     for(i=0;i<argc;++i)
11         printf("argv[%d]=%s\n",i,argv[i]);
12     for(ptr=environ;*ptr!=0;ptr++)
13         printf("%s\n",*ptr);
14     exit(0);
15 }
技術分享圖片

exec執行程序如下:存放在/home/anker/Programs目錄下。

技術分享圖片
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>

char *env_init[] = {"LENGTH=100","PAHT=/tmp",NULL};

int main()
{
    pid_t pid;
    char *path = "/home/anker/Programs/exectest";
    char *filename = "exectest";
    char *argv[3]= {0};
    argv[0] = "exec";
    argv[1] = "test";
    if((pid=fork()) == -1)
    {
        perror("fork() error");
        exit(-1);
    }
    else if(pid == 0)
    {
        printf("Call execle:\n");
        execle(path,argv[0],argv[1],(char*)0,env_init);
    }
    waitpid(pid,NULL,0);

    if((pid=fork()) == -1)
    {
        perror("fork() error");
        exit(-1);
    }
    else if(pid == 0)
    {
        printf("Call execve:\n");
        execve(filename,argv,env_init);
    }
    waitpid(pid,NULL,0);
    exit(0);

}
技術分享圖片

執行結果如下:

技術分享圖片

system函數,在程序中執行一個命令字符串。例如system("date > file")。函數原型如下:

#include <stdlib.h>
int system(const char *command);

system函數實現中調用了fork、exec和waitpid函數,因此有三種返回值。system函數實現一下,沒有信號處理。程序如下:

技術分享圖片
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>

int mysystem(const char * cmdstring);

int main()
{
    int status;
    status = mysystem("date");
    if(status < 0)
    {
       printf("mysystem() error.\n");
    }
    status = mysystem("who");
    if(status < 0)
    {
       printf("mysystem() error.\n");
    }
    exit(0);
}

int mysystem(const char * cmdstring)
{
    pid_t  pid;
    int status;

    if(cmdstring == NULL)
        return 1;
    if((pid = fork()) == -1)
    {
        status = -1;
    }
    else if(pid == 0)   //在子進程中調用shell腳本
    {
        execl("/bin/sh","sh","-c",cmdstring,(char *)0);
        _exit(127);
    }
    else
    {
        while(waitpid(pid,&status,0) < 0)
        {
            if(errno != EINTR)
            {
                status = -1;
                break;
            }
        }
    }
    return status;
}
技術分享圖片

程序執行結果如下:

技術分享圖片

system函數可以設置用戶的ID,這是一個安全漏洞。

進程會計:啟用後,每當進程結束時候內核就寫一個會計記錄,包括命令名、所使用的CPU時間總量、用戶ID和組ID、啟動時間等。accton命令啟動會計處理,會計記錄寫到指定的文件中,Linux中位於/var/account/ pacct。會計記錄結構定義在<sys/acct.h>頭文件中。

#define ACCT_COMM 16

typedef u_int16_t comp_t;

struct acct {
char ac_flag; /* Accounting flags */
u_int16_t ac_uid; /* Accounting user ID */
u_int16_t ac_gid; /* Accounting group ID */
u_int16_t ac_tty; /* Controlling terminal */
u_int32_t ac_btime; /* Process creation time(seconds since the Epoch) */
comp_t ac_utime; /* User CPU time */
comp_t ac_stime; /* System CPU time */
comp_t ac_etime; /* Elapsed time */
comp_t ac_mem; /* Average memory usage (kB) */
comp_t ac_io; /* Characters transferred (unused) */
comp_t ac_rw; /* Blocks read or written (unused) */
comp_t ac_minflt; /* Minor page faults */
comp_t ac_majflt; /* Major page faults */
comp_t ac_swaps; /* Number of swaps (unused) */
u_int32_t ac_exitcode; /* Process termination status(see wait(2)) */
char ac_comm[ACCT_COMM+1];/* Command name (basename of last executed command; null-terminated) */
char ac_pad[X]; /* padding bytes */
};
用戶標識,用getlogin函數獲取用戶的登錄名。函數原型如下:char *getlogin(void)。

進程時間:墻上時鐘時間、用戶CPU時間和系統CPU時間。任一個進程都可以調用times函數獲取它自己及已終止子進程時間。進程時間操作函數及結構如下:

#include <sys/times.h>

clock_t times(struct tms *buf);

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 */
};

總結:通過本章的學習。完全的了解Unix的進程控制,掌握了fork、exec簇、wait和waitpid進程控制函數。另外學習了system函數和進程會計。了解了解釋器文件及其工作方式,用戶標識和進程時間。

Unix環境高級編程(六)進程控制