第七章 進程控制開發[fork() exec exit _exit wait waitpid 守護進程]


前言:

1、fork 創建一個子進程,有兩個返回值。返回0為子進程,返回大於0為父進程。

2、exec 運行新的可執行文件,取代原調用進程的數據段、代碼段和堆棧段。一般是運行fork后,在子進程中執行exec。

3、exit(0)和_exit(0):exit(0)會先清理I/O緩沖后再調用系統exit,而_exit(0)是直接調用系統exit

4、wait函數是用於使父進程(也就是調用wait的進程)阻塞,直到一個子進程結束或者該進程接到了一個指定的信號為止。如果該進程沒有子進程或者他的子進程已經結束,則wait就會立即返回。

5、守護進程使用在android的system下面,如netd,vold等。

====================================================================

7.1.1 Linux 進程相關基本概念

進程是一個程序的一次執行的過程。程序是靜態的,進程是動態的,包括動態創建、調試和消亡的整個過程。

2、進程控制塊

進程是Linux系統的基本調度單位,系統通過進程控制塊描述並表示它的變化。

進程控制塊包含了進程的描述信息、控制信息以及資源信息,它是進程的一個靜態描述。

在Linux中,進程控制塊中的每一項都是一個task_struct結構,它是在include/linux/sched.h中定義的。

3.進程的標識

進程號(PID, Process Idenity Number)   和  父進程號(PPID, parent process ID)

在Linux中獲得當前進程的PID和PPID的系統調用函數為getpid和getppid,

用戶和用戶組標識、進程時間、資源利用情況等。

4.進程運行的狀態 進程是程序的執行過程,根據它的生命期可以划分成3種狀態

執行態/就緒態/等待態

 

7.1.2 Linux下的進程結構

Linux中的進程包含3個段,分別為“數據段”、“代碼段”和“堆棧段”。

數據段存放的是全局變量、常數以及動態數據分配的數據空間(如malloc函數取得的空間)等。

代碼段存放的是程序代碼數據。

堆棧段存放的是子程序的返回地址、子程序的參數以及程序的局部變量。

在Linux系統中,進程的執行模式划分為用戶模式和內核模式。

如果用戶程序執行過程中出現系統調用或者發生中斷事件,就要運行操作系統程序,變成內核模式。

7.1.4 Linux下的進程管理

進程管理分為啟動進程和調度進程。

1.啟動進程 

主要有兩種途徑:手工啟動和調度啟動。手工啟動是由用戶輸入命令直接啟動進程,而調度啟動是指系統根據用戶的設置自行啟動進程。

(1)手工啟動進程又可分為前台啟動和后台啟動。

前台啟動是手工啟動一個進程的最常用方式。一般地,當用戶鍵入一個命令如"ls -l"時,就已經啟動了一個進程,並且是一個前台進程。

后台啟動往往是在該進程非常耗時,且用戶也不急着需要結果的時候啟動的。比如格式化文本文件的進程。

(2)調度啟動

費時且占用資源的維護工作,並且在深夜無人職守的時候進行,用戶可以事先進行調度安排,指定任務運行的時間或者場合。

使用調度啟動進程有幾個常用的命令,如at命令在指定時刻執行相關進程,cron命令可以自動周期性的執行相關進程。

2.調度進程

調度進程包括對進程的中斷操作、改變優先級、查看進程狀態等。

ps | top | nice | renice | kill | crontab | bg

 

======================================================================

7.2 Linux進程控制編程

進程創建

1. fork()

pid_t fork(void);

在Linux中創建一個新進程的惟一方法是使用fork函數。它執行一次卻返回兩個值。

(1) fork函數說明

fork函數用於從已存在進程中創建一個新進程。新進程稱為子進程,而原進程稱為父進程。

這兩個分別帶回它們各自的返回值,其中父進程的返回值是子進程的進程號,而子進程則返回0。因此,可以通過返回值來判斷該進程是父進程還是子進程。

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int main(void){
    pid_t result;

    result = fork();
    if(result == -1){
      perror("fork");
      exit(1);
    } else if(result == 0){
      printf("The return value is 0,In child process!! My PID is %d\n",getpid());
    } else {
      printf("The return value is %d, In father process !! My PID is %d\n", result, getpid());
    }
}
fork

 

(4)函數使用注意點

2. exec函數族

(1)exec函數族說明

fork函數用於創建一個子進程,該子進程幾乎copy了父進程的全部內容,但是這個新創建的進程如何執行呢?

這個exec函數族就提供了一個在進程中啟動另一個程序執行的方法,它可以根據指定的文件名或目錄名找到可執行文件。

在Linux中使用exec函數族主要有兩種情況:

*當進程認為自己不能再為系統和用戶做出任何貢獻時,就可以調用任何exec函數族讓自己重生

*如果一個進程想執行另一個程序,那么它就可以調用fork函數新建一個進程,然后調用任何一個exec,這樣看起來就好像通過執行應用程序而產生一個新進程。(這種情況非常普遍)

(2)exec函數族語法 函數族里有6個以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, ...)從環境變量$PATH所指出的路徑中進行查找

int execvp(const char *file, char * const argv[])從環境變量$PATH所指出的路徑中進行查找

參數傳遞方式有兩種:一種是逐個列舉的方式,另一種是將所有參數整體構造指針數組傳遞。

字母為“l"(list)的表示逐個列舉,其語法為char *arg;字母為"v"(vector)的表示將所有參數整體構造指針數組傳遞,其語法為*const argv[]。

(3)使用實例

execlp
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
    if(fork() == 0){ 
        /*call execlp, just like call "ps -ef"*/
        if(execlp("ps","ps","-ef",NULL) < 0)
            perror("execlp error!");
    }   
}
execl
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int main(){
    if(fork() == 0){ 
        if(execl("/bin/ps", "ps", "-ef", NULL) < 0)
            perror("execl error!");
    }   
}
execle
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    /*命令參數列表,必須以 NULL 結尾*/
    char *envp[]={"PATH=/tmp","USER=sunq",NULL};
    if(fork()==0){
        /*調用 execle 函數,注意這里也要指出 env 的完整路徑*/
        if(execle("/usr/bin/env","env",NULL,envp)<0)
            perror("execle error!");
    }   
}
execve
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    /*命令參數列表,必須以 NULL 結尾*/
    char *arg[]={"env",NULL};
    char *envp[]={"PATH=/tmp","USER=sunq",NULL};
    if(fork()==0){
        if(execve("/usr/bin/env",arg,envp)<0)
            perror("execve error!");
    }   
}

 

3.exit和 _exit

(1)函數說明,exit和_exit函數都是用來終止進程的,進程會無條件的停止剩下的所有操作。

但這兩個函數還是有區別的

_exit() 直接調用 exit系統調用

exit()->調用退出處理函數->清理I/O緩沖->調用exit系統調用

若想保證數據的完整性,就一定要使用exit()函數,因為程序處理數據有緩沖區。

由於printf函數使用的緩沖I/O方式,該函數在遇到"\n"換行符時自動從緩沖區中將記錄讀出。

如果沒有"\n",exit(0)能從緩沖區讀出,而_exit(0)則不能。

 

4. wait 和 waitpid

(1)wait和waitpid函數說明

wait函數是用於使父進程(也就是調用wait的進程)阻塞,直到一個子進程結束或者該進程接到了一個指定的信號為止。

如果該進程沒有子進程或者他的子進程已經結束,則wait就會立即返回。

waitpid的作用和wait一樣,但它並不一定要等待第一個終止的子進程,它還有若干選項。wait只是waitpid的一個特例

(2)wait和waitpid函數格式說明

pid_t wait(int *status)

pid_t waitpid(pid_t pid, int *status, int options) 

(3) waitpid使用實例

本例中首先使用fork()新建一子進程,然后讓其子進程暫停5s,接下來對原有的父進程使用waitpid函數,並使用參數WNOHANG使該父進程不會阻塞。若有子進程退出,則waitpid返回子進程號;若沒有子進程限出,則waitpid返回0,並且父進程每隔一秒循環判斷一次。

waitpid
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    pid_t pc,pr;
    pc=fork();  
    if(pc<0)
        printf("Error fork.\n");
    /*子進程*/
    else if(pc==0){
        /*子進程暫停 5s*/
        sleep(5);
        /*子進程正常退出*/
        exit(0);
    }   
    /*父進程*/
    else{
        /*循環測試子進程是否退出*/
        do{ 
            /*調用 waitpid,且父進程不阻塞*/
            pr=waitpid(pc,NULL,WNOHANG);
            /*若子進程還未退出,則父進程暫停 1s*/
            if(pr==0){
                printf("The child process has not exited\n");
                sleep(1);
            }    
        }while(pr==0);
        /*若發現子進程退出,打印出相應情況*/
        if(pr==pc)
            printf("Get child %d\n",pr);
        else
            printf("some error occured.\n");
    }   
}

 

7.3 Linux守護進程

1.創建子進程,父進程退出

pid = fork();

if(pid>0)

    exit(0);父進程退出了。

子進程變成了孤兒進程,會自動由1號進程(也就是init進程)收養它。這樣,原先的子進程就會變成init進程的子進程了。

 

2.在子進程中創建新會話

pid_t setsid(void)

進程組:進程組是一個或多個進程的集合。進程組由進程組ID來惟一標識。除了進程號PID之后,進程組ID也是一個進程的必備屬性。每個進程組都有一個組長進程,其組長進程的進程號等於進程組ID,且該進程ID不會因組長進程的退出而受到影響。

會話期:會話組是一個或多個進程組的集合。通常,一個會話開始於用戶登錄,終止於用戶退出,在此期間該用戶運行的所有進程都屬於這個會話期,它們之間的關系如下圖所示。

setsid函數作用:

setsid函數用於創建一個新的會話,並擔任該會話組的組長。

讓進程擺脫原會話的控制。讓進程擺脫原進程組的控制。讓進程擺脫原控制終端的控制。

 

由於調用fork函數時,子進程全盤拷貝了父進程的會話期、進程組控制終端等,雖然父進程退出了,但原先的會話期、進程組、控制終端等並沒有改變。

3.改變當前目錄為根目錄

讓"/"作為守護進程的當前工作目錄。常見函數為chdir

4.重設文件權限掩碼 

umask(0)

5.關閉文件描述符

for(i = 0; i<MAXFILE; i++)

  close(i);

dameon
/*dameon.c 創建守護進程實例*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAXFILE 65535
int main()
{
    pid_t pc; 
    int i,fd,len;
    char *buf="This is a Dameon\n";
    len =strlen(buf);
    pc=fork(); //第一步
    if(pc<0){
        printf("error fork\n");
        exit(1);
    }else if(pc>0)
        exit(0);
    /*第二步*/
    setsid();
    /*第三步*/
    chdir("/");
    /*第四步*/
    umask(0);
    for(i=0;i<MAXFILE;i++)
        /*第五步*/
        close(i);
    /*這時創建完守護進程,以下開始正式進入守護進程工作*/
    while(1){
        if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0){
            perror("open");
            exit(1);
        }   
        write(fd, buf, len+1);
        close(fd);
        sleep(10);
    }   
}

 

7.3.3 守護進程的出錯處理

gdb是通過輸出錯誤信息到控制終端來通知程序員

守護進程使用syslog服務,將程序中的出錯信息輸入到“/var/log/messages"系統日志文件中。

syslog是Linux中的系統日志管理服務,通過守護進程syslogd來維護。該守護進程在啟動時會讀一個配置文件"/etc/syslog.conf"。該文件決定了不同種類的消息會發送向何處。該機制提供了3個syslog函數,分別為openlog、syslog和closelog。

void openlog(char *ident, int option, int facility)

void syslog(int priority, char *format, ...)

void closelog(void)

(3)使用實例

 

7.4實驗內容

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM