1.進(jìn)程創(chuàng)建:
1.1fork函數(shù)
在linux中fork函數(shù)時(shí)非常重要的函數(shù),它從已存在進(jìn)程中創(chuàng)建一個(gè)新進(jìn)程。新進(jìn)程為子進(jìn)程,而原進(jìn)程為父進(jìn)程。
\#include <unistd.h>
pid_t fork(void);
返回值:子進(jìn)程中返回0,父進(jìn)程返回子進(jìn)程id,出錯(cuò)返回-1
進(jìn)程調(diào)用fork,當(dāng)控制轉(zhuǎn)移到內(nèi)核中的fork代碼后,內(nèi)核做:
- 分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程
- 將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝至子進(jìn)程
- 添加子進(jìn)程到系統(tǒng)進(jìn)程列表當(dāng)中
- fork返回,開始調(diào)度器調(diào)度
??當(dāng)準(zhǔn)備返回時(shí),上面三個(gè)工作都有了,父進(jìn)程繼續(xù)執(zhí)行開始 return
,子進(jìn)程也可能執(zhí)行 fork
的返回值,然后就會(huì)得到兩次返回。
具體我們可以下面這段代碼:
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
運(yùn)行結(jié)果如下:
這里看到了三行輸出,一行before,兩行after。進(jìn)程15256先打印before消息,然后它有打印after。另一個(gè)after
消息有15257打印的。注意到進(jìn)程15257沒有打印before,為什么呢?如下圖所示
所以,fork之前父進(jìn)程獨(dú)立執(zhí)行,fork之后,父子兩個(gè)執(zhí)行流分別執(zhí)行。注意,fork之后,誰先執(zhí)行完全由調(diào)度器
決定
fork之后,子進(jìn)程的代碼從fork開始往后執(zhí)行,那OS怎么知道從哪里開始執(zhí)行?
eip程序計(jì)數(shù)器會(huì)出手
創(chuàng)建子進(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu):
(struct task_struct + struct mm_struct + 頁表)+ 代碼繼承父進(jìn)程,數(shù)據(jù)以寫時(shí)拷貝的方式來進(jìn)行共享或者獨(dú)立。
fork
之后創(chuàng)建一批結(jié)構(gòu),代碼以共享的方式,數(shù)據(jù)以寫時(shí)拷貝的方式,兩個(gè)進(jìn)程必須保證 “獨(dú)立性”,做到互不影響。在這種共享機(jī)制下子進(jìn)程或父進(jìn)程任何一方掛掉,不會(huì)影響另一個(gè)進(jìn)程。
1.2寫時(shí)拷貝:
通常,父子代碼共享,父子再不寫入時(shí),數(shù)據(jù)也是共享的,當(dāng)任意一方試圖寫入,便以寫時(shí)拷貝的方式各自一份副
本。具體見下圖:
?為什么需要寫時(shí)拷貝呢?
- 有浪費(fèi)空間之嫌:父進(jìn)程的數(shù)據(jù),子進(jìn)程不一定全用;即便使用,也不一定全部寫入。
- 最理想的情況,只有會(huì)被父子修改的數(shù)據(jù),進(jìn)行分離拷貝。不需要修改的數(shù)據(jù),共享即可。但是從技術(shù)角度實(shí)現(xiàn)復(fù)雜。
- 如果
fork
的時(shí)候,就無腦拷貝數(shù)據(jù)給子進(jìn)程,會(huì)增加fork
的成本(內(nèi)存和時(shí)間)
我們返回去看上圖,修改內(nèi)容前后,代碼是共享同一塊的,但是數(shù)據(jù)是發(fā)生寫時(shí)拷貝的,當(dāng)修改后代碼段會(huì)指向不同的物理內(nèi)存
最終采用寫時(shí)拷貝:只會(huì)拷貝父子修改的、變相的,就是拷貝數(shù)據(jù)的最小成本。拷貝的成本依舊存在。
寫時(shí)拷貝實(shí)際上以一種 延遲拷貝策略,延遲拷貝最大的價(jià)值:只有真正使用的時(shí)候才給你拷。
其最大的意義在于,你想要,但是不立馬使用的空間,先不給你,那么也就意味著可以先給別人。
反正拷貝的成本總是要有,早給你晚給你都是一樣。萬一我現(xiàn)在給你你又不用,那其實(shí)不很浪費(fèi)
所以我選擇暫時(shí)先不給你,等你什么時(shí)候要用什么時(shí)候再給。這就變相的提高了內(nèi)存的使用情況。
舉個(gè)例子
?我們?cè)贑語言的時(shí)候發(fā)現(xiàn)char*類型(字符串)不可被修改
??原因在于在頁表項(xiàng)的位置的時(shí)候設(shè)置只讀屬性
寫時(shí)拷貝:只有當(dāng)子進(jìn)程要進(jìn)行修改的時(shí)候才給子進(jìn)程分配空間,本質(zhì)是一個(gè)資源篩選或者叫做按需申請(qǐng)資源的策略
1.3fork函數(shù)返回值
- 子進(jìn)程返回0,
- 父進(jìn)程返回的是子進(jìn)程的pid。
1.4fork常規(guī)用法
一個(gè)父進(jìn)程希望復(fù)制自己,使父子進(jìn)程同時(shí)執(zhí)行不同的代碼段。例如,父進(jìn)程等待客戶端請(qǐng)求,生成子
進(jìn)程來處理請(qǐng)求。
一個(gè)進(jìn)程要執(zhí)行一個(gè)不同的程序。例如子進(jìn)程從fork返回后,調(diào)用exec函數(shù)。
1.5fork調(diào)用失敗的原因
fork函數(shù)創(chuàng)建子進(jìn)程也可能會(huì)失敗,有以下兩種情況:
- 系統(tǒng)中有太多的進(jìn)程,內(nèi)存空間不足,子進(jìn)程創(chuàng)建失敗。
- 實(shí)際用戶的進(jìn)程數(shù)超過了限制,子進(jìn)程創(chuàng)建失敗。
2.進(jìn)程終止
進(jìn)程退出場(chǎng)景
- 代碼運(yùn)行完畢,結(jié)果正確
- 代碼運(yùn)行完畢,結(jié)果不正確
- 代碼異常終止
崩潰的本質(zhì):進(jìn)程因?yàn)槟承┰?,?dǎo)致進(jìn)程收到了來自操作系統(tǒng)的信號(hào)
return 進(jìn)程的退出碼反映結(jié)果是否正確,可以供用戶進(jìn)行進(jìn)程退出健康狀態(tài)的判斷
int main()
{
return 0;
}
返回值為 0
,表示進(jìn)程代碼跑完,結(jié)果是否正確,我們用 0
表示成功,非 0
表示失敗。
2.1進(jìn)程退出碼
我們都知道m(xù)ain函數(shù)是代碼的入口,但實(shí)際上main函數(shù)只是用戶級(jí)別代碼的入口,main函數(shù)也是被其他函數(shù)調(diào)用的,例如在VS2013當(dāng)中main函數(shù)就是被一個(gè)名為__tmainCRTStartup的函數(shù)所調(diào)用,而__tmainCRTStartup函數(shù)又是通過加載器被操作系統(tǒng)所調(diào)用的,也就是說main函數(shù)是間接性被操作系統(tǒng)所調(diào)用的。
既然main函數(shù)是間接性被操作系統(tǒng)所調(diào)用的,那么當(dāng)main函數(shù)調(diào)用結(jié)束后就應(yīng)該給操作系統(tǒng)返回相應(yīng)的退出信息,而這個(gè)所謂的退出信息就是以退出碼的形式作為main函數(shù)的返回值返回,我們一般以0表示代碼成功執(zhí)行完畢,以非0表示代碼執(zhí)行過程中出現(xiàn)錯(cuò)誤,這就是為什么我們都在main函數(shù)的最后返回0的原因。
??我們可以利用 echo $?
來查看最近的進(jìn)程的退出碼,$?只會(huì)保留最近一次執(zhí)行的進(jìn)程的退出碼
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main( void )
{
return 1;
}
我們?cè)俅螆?zhí)行后發(fā)現(xiàn)退出碼變成0了
.我們發(fā)現(xiàn)這些錯(cuò)誤碼都是用數(shù)字來表示,我們來查看各種退出碼的含義
C 語言當(dāng)中有個(gè)的 string.h``
中有一個(gè) strerror
接口,是最經(jīng)典的、將錯(cuò)誤碼表述打印出來的接口,
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* Nomedium found */
#define EMEDIUMTYEP 124 /*Wrongmedium found */
#define ECANCELED 125 /* Operation Canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
其中,0
表示 success,1 表示權(quán)限不允許,2 找不到文件或目錄
2.2進(jìn)程終止的方式
exit是C庫的接口,_exit是系統(tǒng)接口,
_exit函數(shù)
#include <unistd.h>
void _exit(int status);
參數(shù):status 定義了進(jìn)程的終止?fàn)顟B(tài),父進(jìn)程通過wait來獲取該值
說明:雖然status是int,但是僅有低8位可以被父進(jìn)程所用。所以_exit(-1)時(shí),在終端執(zhí)行$?發(fā)現(xiàn)返回值
是255。
exit函數(shù)
#include <unistd.h>
void exit(int status);
exit最后也會(huì)調(diào)用_exit, 但在調(diào)用_exit之前,還做了其他工作:
- 執(zhí)行用戶通過 atexit或on_exit定義的清理函數(shù)。
- 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
- 調(diào)用_exit
驗(yàn)證執(zhí)行_exit之前會(huì)沖刷緩沖區(qū)
3.進(jìn)程等待
3.1進(jìn)程等待必要性
-
之前講過,子進(jìn)程退出,父進(jìn)程如果不管不顧,就可能造成‘僵尸進(jìn)程’的問題,進(jìn)而造成內(nèi)存泄漏。
-
另外,進(jìn)程一旦變成僵尸狀態(tài),那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因?yàn)檎l也沒有辦法
殺死一個(gè)已經(jīng)死去的進(jìn)程。
-
最后,父進(jìn)程派給子進(jìn)程的任務(wù)完成的如何,我們需要知道。如,子進(jìn)程運(yùn)行完成,結(jié)果對(duì)還是不對(duì),
或者是否正常退出。
-
父進(jìn)程通過進(jìn)程等待的方式,回收子進(jìn)程資源,獲取子進(jìn)程退出信息
3.2進(jìn)程等待的方法
如何獲取status
wait和waitpid,都有一個(gè)status參數(shù),該參數(shù)是一個(gè)輸出型參數(shù),由操作系統(tǒng)填充。
如果傳遞NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息。
否則,操作系統(tǒng)會(huì)根據(jù)該參數(shù),將子進(jìn)程的退出信息反饋給父進(jìn)程。
status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16比特
位):
在status的低16比特位當(dāng)中,高8位表示進(jìn)程的退出狀態(tài),即退出碼。進(jìn)程若是被信號(hào)所殺,則低7位表示終止信號(hào),而第8位比特位是core dump標(biāo)志。
我們通過一系列位操作,就可以根據(jù)status得到進(jìn)程的退出碼和退出信號(hào)。
exitCode = (status >> 8) & 0xFF; //退出碼
exitSignal = status & 0x7F; //退出信號(hào)
對(duì)于此,系統(tǒng)當(dāng)中提供了兩個(gè)宏來獲取退出碼和退出信號(hào)。
- WIFEXITED(status):用于查看進(jìn)程是否是正常退出,本質(zhì)是檢查是否收到信號(hào)。
- WEXITSTATUS(status):用于獲取進(jìn)程的退出碼。
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待進(jìn)程pid,失敗返回-1。
參數(shù):
輸出型參數(shù),獲取子進(jìn)程退出狀態(tài),不關(guān)心則可以設(shè)置成為NULL
父進(jìn)程等待子進(jìn)程
這里子進(jìn)程會(huì)跑5s,父進(jìn)程一進(jìn)來就是10s休息,也就是未來會(huì)有有5s子進(jìn)程已經(jīng)退了,父進(jìn)程還在,這里子進(jìn)程的狀態(tài)應(yīng)該是Z,然后5s后父進(jìn)程開始回收,在進(jìn)入5s,我們會(huì)看到第二個(gè)現(xiàn)象,子進(jìn)程要消失,z狀態(tài)要退出來了,父進(jìn)程在跑,再過5s父進(jìn)程退出來
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當(dāng)正常返回的時(shí)候waitpid返回收集到的子進(jìn)程的進(jìn)程ID;
如果設(shè)置了選項(xiàng)WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進(jìn)程可收集,則返回0;
如果調(diào)用中出錯(cuò),則返回-1,這時(shí)errno會(huì)被設(shè)置成相應(yīng)的值以指示錯(cuò)誤所在;
參數(shù):
pid:
Pid=-1,等待任一個(gè)子進(jìn)程。與wait等效。
Pid>0.等待其進(jìn)程ID與pid相等的子進(jìn)程。
status:
WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的退出碼)
options:(阻塞,后面會(huì)講)
WNOHANG: 若pid指定的子進(jìn)程沒有結(jié)束,則waitpid()函數(shù)返回0,不予以等待。若正常結(jié)束,則返回該子進(jìn)
程的ID
如果子進(jìn)程已經(jīng)退出,調(diào)用wait/waitpid時(shí),wait/waitpid會(huì)立即返回,并且釋放資源,獲得子進(jìn)程退
出信息。
如果在任意時(shí)刻調(diào)用wait/waitpid,子進(jìn)程存在且正常運(yùn)行,則進(jìn)程可能阻塞。
如果不存在該子進(jìn)程,則立即出錯(cuò)返回。
所以第二個(gè)參數(shù)要獲取兩個(gè)整數(shù),不要當(dāng)做一個(gè)完整的整數(shù),而應(yīng)該看做位圖
我們可以通過kill -l
來觀察所有的信號(hào),我們發(fā)現(xiàn),沒有0號(hào)信號(hào)
我們來查看一下退出狀態(tài)和退出信號(hào)
?請(qǐng)問父進(jìn)程是如何獲取子進(jìn)程的退出信息的呢???通過子進(jìn)程的pcb信息
說明:父進(jìn)程等待子進(jìn)程,子進(jìn)程也會(huì)執(zhí)行自己的代碼。當(dāng)子進(jìn)程執(zhí)行了 return/exit 退出后,子進(jìn)程會(huì)將自己的退出碼信息寫入自己的進(jìn)程控制塊 () 中。子進(jìn)程退出了,代碼可以釋放,子進(jìn)程退出后變成 Z 狀態(tài),其本質(zhì)上就是將自己的 task_struct 維護(hù)起來(代碼可以釋放,但是 task_struct 必須維護(hù))。所謂的 wait/waitpid 的退出信息,實(shí)際上就是從子進(jìn)程的 task_struct 中拿出來的,即 從子進(jìn)程的 task_struct 中拿出子進(jìn)程退出的退出碼。
所以,我們的父進(jìn)程在等待子進(jìn)程死亡,等子進(jìn)程一死,就直接把子進(jìn)程的退出碼信息拷貝過去,通過 wait/waitpid 傳進(jìn)來的參數(shù)后,父進(jìn)程就拿到了子進(jìn)程的退出結(jié)果。即 子進(jìn)程會(huì)將自己的退出信息寫入 task_struct 。
4.進(jìn)程阻塞
?父進(jìn)程在wait的時(shí)候,如果子進(jìn)程沒有退出,父進(jìn)程在干嘛?
??父進(jìn)程只能一直在調(diào)用waitpid進(jìn)行等待——阻塞等待
子進(jìn)程的task_struct里面有一個(gè)parent指針,子進(jìn)程一旦退出,通過這種指針逆向找回去將父進(jìn)程的狀態(tài)從S變R
4.1輪詢檢測(cè)
所謂的阻塞,其實(shí)就是掛起。在上層表現(xiàn)來看,就是進(jìn)程卡住了(比如 scanf,cin 等)。
而非阻塞式等待是 “巧等”,會(huì)做些自己的事,而不是一屁股做那傻等!
多次調(diào)用非阻塞接口,這個(gè)過程我們稱之為 輪詢檢測(cè) (Polling)。
我們上一章中講解 waitpid 時(shí),舉的例子都是 阻塞式 的等待。
如果我們想 非阻塞式 的等,我們可以設(shè)置 options 選項(xiàng)為 WNOHANG
。
這樣一來,等待的子進(jìn)程若是沒有結(jié)束,那么waitpid函數(shù)將直接返回0,不予以等待。而等待的子進(jìn)程若是正常結(jié)束,則返回該子進(jìn)程的pid。
4.2基于非阻塞的輪詢等待
例如,父進(jìn)程可以隔一段時(shí)間調(diào)用一次waitpid函數(shù),若是等待的子進(jìn)程尚未退出,則父進(jìn)程可以先去做一些其他事,過一段時(shí)間再調(diào)用waitpid函數(shù)讀取子進(jìn)程的退出信息。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if (id == 0){
//child
int count = 3;
while (count--){
printf("child do something...PID:%d, PPID:%d\n", getpid(), getppid());
sleep(3);
}
exit(0);
}
//father
while (1){
int status = 0;
pid_t ret = waitpid(id, &status, WNOHANG);
if (ret > 0){
printf("wait child success...\n");
printf("exit code:%d\n", WEXITSTATUS(status));
break;
}
else if (ret == 0){
printf("father do other things...\n");
sleep(1);
}
else{
printf("waitpid error...\n");
break;
}
}
return 0;
}
運(yùn)行結(jié)果就是,父進(jìn)程每隔一段時(shí)間就去查看子進(jìn)程是否退出,若未退出,則父進(jìn)程先去忙自己的事情,過一段時(shí)間再來查看,直到子進(jìn)程退出后讀取子進(jìn)程的退出信息。
5.進(jìn)程的程序替換
創(chuàng)建子進(jìn)程的目的是什么?就是為了讓子進(jìn)程幫我執(zhí)行待定的任務(wù)
- 讓子進(jìn)程執(zhí)行父進(jìn)程的一部分代碼
- 如果子進(jìn)程執(zhí)行一個(gè)全新的程序代碼?可以進(jìn)行進(jìn)程的程序替換
我們?nèi)绾巫屪舆M(jìn)程執(zhí)行一個(gè)新的代碼呢?
之前我們通過寫時(shí)拷貝,讓子進(jìn)程和父進(jìn)程在數(shù)據(jù)上互相解耦,保證獨(dú)立性。如果想讓子進(jìn)程和父進(jìn)程徹底分開,讓子進(jìn)程徹徹底底地執(zhí)行一個(gè)全新的程序,我們就需要 進(jìn)程的程序替換。
若想讓子進(jìn)程執(zhí)行另一個(gè)程序,往往需要調(diào)用一種exec函數(shù)。
當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,并從新程序的啟動(dòng)例程開始執(zhí)行。
5.1程序替換原理
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)
以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)
例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
- 將磁盤中的內(nèi)存,加載入內(nèi)存結(jié)構(gòu)。
- 重新建立頁表映射,設(shè)執(zhí)行程序替換,就重新建立誰的映射(下圖為子進(jìn)程建立)。
- 效果:讓父進(jìn)程和子進(jìn)程徹底分離,并讓子進(jìn)程執(zhí)行一個(gè)全新的程序!
這個(gè)過程有沒有創(chuàng)建新的進(jìn)程呢?沒有!根本就沒有創(chuàng)建新的進(jìn)程!
因?yàn)樽舆M(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu)根本沒變,只是重新建立了虛擬的物理地址之間的映射關(guān)系罷了。
內(nèi)核數(shù)據(jù)結(jié)構(gòu)沒有發(fā)生任何變化! 包括子進(jìn)程的 , 都不變,說明壓根沒有創(chuàng)建新進(jìn)程。exec函數(shù)
5.2替換函數(shù)execl簇
其實(shí)有六種以exec開頭的函數(shù),統(tǒng)稱exec函數(shù):
#include <unistd.h>`
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 *path, char *const argv[], char *const envp[]);
見見豬跑execl函數(shù):
int execl(const char* path, const char& arg, ...);
- 它的第一個(gè)參數(shù)是 path,屬于路徑。
- 參數(shù) const char* arg, … 中的 … 表示可變參數(shù),命令行怎么寫(ls, -l, -a) 這個(gè)參數(shù)就怎么填。ls, -l, -a 最后必須以 NULL 結(jié)尾,表示 “如何執(zhí)行程序的” 參數(shù)傳遞完畢
ls就是一個(gè)可執(zhí)行程序,讓一個(gè)進(jìn)程去執(zhí)行一個(gè)在磁盤的程序
站在程序的角度:
它就是被動(dòng)的加載到內(nèi)存,這個(gè)程序被加載了,那以后這些函數(shù)被稱為加載器
既然我自己寫的代碼能加載新的程序,那么操作系統(tǒng)呢?
?當(dāng)創(chuàng)建進(jìn)程的時(shí)候,先有進(jìn)程的數(shù)據(jù)結(jié)構(gòu),還是先加載代碼和數(shù)據(jù)?
??先有數(shù)據(jù)結(jié)構(gòu),再把外部的代碼弄進(jìn)去,把這個(gè)行為叫做加載
?為什么上述測(cè)試代碼我們只看到begin沒有看到end呢?
??執(zhí)行程序替換,新的代碼和數(shù)據(jù)就被加載了,后續(xù)的代碼屬于老代碼,直接被替換了,沒有機(jī)會(huì)執(zhí)行了
程序替換是整體替換,不能局部替換
程序替換只會(huì)影響調(diào)用進(jìn)程,進(jìn)程具有獨(dú)立性!
今天子進(jìn)程加載新程序的時(shí)候,是需要進(jìn)行程序替換,發(fā)生寫時(shí)拷貝(子進(jìn)程執(zhí)行的可是全新的程序,新的代碼!寫時(shí)拷貝在代碼區(qū)也可以發(fā)生?。?/p>
?execl是函數(shù)嗎?函數(shù)調(diào)用可能會(huì)失敗嗎?
??是函數(shù),可能會(huì)失敗,失敗了就不會(huì)調(diào)用新程序運(yùn)行就會(huì)執(zhí)行后面的程序
execl:如果替換成功,不會(huì)有返回值,如果替換失敗,一定有返回值——如果失敗了,必定返回——只要有返回值,就失敗了
因此不用對(duì)該函數(shù)進(jìn)行返回值判斷,只要繼續(xù)向后運(yùn)行一定是失敗的
execv函數(shù)
int execv(const char *path, char *const argv[]);
這里的v代表的是vector,這里傳參要傳一個(gè)數(shù)組
execlp函數(shù)
int execlp(const char *file, const char *arg, ...);
帶p的意義:當(dāng)我們執(zhí)行指定程序的時(shí)候,只需要指定程序名即可,系統(tǒng)會(huì)自動(dòng)在環(huán)境變量path中盡顯查找
?這兩個(gè)ls一樣嗎???不一樣,第一個(gè)參數(shù)是 “供系統(tǒng)去找你是誰的”,后面的一坨代表的是 “你想怎么去執(zhí)行它”
exevp函數(shù)
int execvp(const char *file, char *const argv[]);
這個(gè)其實(shí)就是前面兩個(gè)的結(jié)合
execle函數(shù)
int execle(const char *path, const char *arg, ..., char * const envp[]);
envp是自定義的環(huán)境變量
execvpe函數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-713912.html
int execvpe(const char* file, char* const argv[], char* const envp[]);
事實(shí)上,只有execve才是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都是調(diào)用的execve,所以execve在man手冊(cè)的第2節(jié),而其它五個(gè)函數(shù)在man手冊(cè)的第3節(jié),也就是說其他五個(gè)函數(shù)實(shí)際上是對(duì)系統(tǒng)調(diào)用execve進(jìn)行了封裝,以滿足不同用戶的不同調(diào)用場(chǎng)景的。
char *file, const char *arg, …);文章來源地址http://www.zghlxwxcb.cn/news/detail-713912.html
到了這里,關(guān)于【Linux進(jìn)行時(shí)】進(jìn)程控制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!