前言:
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()); } }
(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)使用實例

#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!"); } }

#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!"); } }

#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!"); } }

#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,並且父進程每隔一秒循環判斷一次。

#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.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實驗內容