fork()
fork()將父進(jìn)程復(fù)制一份子進(jìn)程, 在子進(jìn)程中從fork()調(diào)用處繼續(xù)執(zhí)行, 之后的代碼在父子進(jìn)程中各自執(zhí)行一遍. 最終父進(jìn)程的fork()返回子進(jìn)程的pid, 子進(jìn)程的fork()返回0表示創(chuàng)建成功. 所以看起來仿佛fork()返回兩個(gè)返回值, 其實(shí)是兩個(gè)進(jìn)程的fork()各自的返回值, 通過返回值不同區(qū)分父子進(jìn)程.
getpid()
獲取當(dāng)前進(jìn)程pid, getppid()
獲取父進(jìn)程pid.
getuid()
獲取當(dāng)前進(jìn)程實(shí)際用戶, geteuid()
獲取當(dāng)前進(jìn)程有效用戶. 如使用sudo命令時(shí)shell進(jìn)程有效用戶就變?yōu)閞oot, 但實(shí)際用戶還是username.
循環(huán)創(chuàng)建子進(jìn)程如下所示:
當(dāng)pid==0即在子進(jìn)程中時(shí)要跳出循環(huán), 避免創(chuàng)建"孫進(jìn)程". 使得只有主進(jìn)程有調(diào)用fork
父子進(jìn)程各自的代碼段是獨(dú)立的, 各自的全局變量也是獨(dú)立的, 但對(duì)于只讀操作可以共享同一塊物理內(nèi)存, 寫時(shí)再復(fù)制, 虛擬地址空間還是獨(dú)立的.
父子進(jìn)程誰先執(zhí)行并不一定.
gdb使用set follow-fork-mode child/parent
來跟蹤父進(jìn)程或子進(jìn)程, 注意要在運(yùn)行到 fork()前設(shè)置.
exec()函數(shù)族
調(diào)用exec函數(shù)會(huì)將當(dāng)前進(jìn)程的.text,.data段完全替換為新程序的.text和.data段, 但是不創(chuàng)建新進(jìn)程, 所以進(jìn)程id不變.
頭文件<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[]);
execl()/execlp()/execle()
execl("/bin/ls", "ls", "-l", "-a", NULL)
直接傳入文件路徑, 不需要PATH環(huán)境變量, 第二個(gè)參數(shù)起是命令行參數(shù)argv[0], argv[1]..., 命令行參數(shù)也是一個(gè)字符串?dāng)?shù)組, 結(jié)尾的哨兵NULL要顯式的寫出來.
execlp("ls", "ls", "-l", "-a", NULL)
該函數(shù)需要配合PATH來搜索需要加載的程序. 其第一個(gè)參數(shù)ls指的是要從PATH中查找的程序; 第二個(gè)參數(shù)ls指的是argv[0], 表示要被調(diào)用的程序. 此處文件中不需要引入環(huán)境變量extern char** environ
, 函數(shù)會(huì)自動(dòng)去查找. 其中environ是一個(gè)指針數(shù)組, 每一個(gè)指針指向一個(gè)char*字符串. execlp()通常用來調(diào)用系統(tǒng)程序如ls, date, cp, cat等命令.
先聲明char* const envp[] = {"AA=11", "BB=22", NULL};
然后將envp傳入execle("/bin/ls", "ls", "-l", "-a", NULL, envp)
. 原進(jìn)程會(huì)將環(huán)境變量信息傳遞給新進(jìn)程, 可以利用execle函數(shù)傳遞自己需要的環(huán)境變量信息.
execv()/execvp()/execve()
先聲明char* argv[]={"ls", "-l", "-a", NULL};char* const envp[] = {"AA=11", "BB=22", NULL};
然后execv("/bin/ls", argv)
直接聲明一個(gè)命令行參數(shù)數(shù)組, 再傳進(jìn)去. 這里的v指代argv. 同理, execvp("ls", argv)
需要借助PATH, execve("/bin/ls", argv, envp)
需要傳入環(huán)境變量.
若想將ps -aux輸出到屏幕的內(nèi)容輸出到一個(gè)文件中, 可以使用dup2(fd, STDOUT_FILENO)
將文件描述符表1號(hào)的STDOUT_FILENO指向?qū)?yīng)文件的fd, 然后再execlp("ps", "ps", "aux", NULL)
.
注:1. 上述exec系列函數(shù)底層都是通過execve()系統(tǒng)調(diào)用實(shí)現(xiàn), 2. exec函數(shù)族成功了不返回值, 失敗了才返回errno, 所以后面的close(fd)實(shí)際上是不執(zhí)行的, 不過依賴于隱式回收可以在進(jìn)程結(jié)束時(shí)自動(dòng)關(guān)閉文件, 所以不寫close(fd)不會(huì)出錯(cuò). 如果exec失敗也不需要if判斷, 直接perror接exit即可
孤兒進(jìn)程
若父進(jìn)程先于子進(jìn)程結(jié)束, 則子進(jìn)程成為孤兒進(jìn)程, 其被/sbin/init進(jìn)程領(lǐng)養(yǎng)(pid=1)或是/usr/sbin/init進(jìn)程, 然后被回收.
僵尸進(jìn)程
子進(jìn)程死亡后父進(jìn)程未處理, 則子進(jìn)程成為僵尸進(jìn)程, PCB仍存放在內(nèi)核中. 使用ps aux查看時(shí)發(fā)現(xiàn)進(jìn)程名變成[進(jìn)程名]<defunct>, 表示是一個(gè)僵尸進(jìn)程, 狀態(tài)為Z. 另外運(yùn)行中的進(jìn)程狀態(tài)是R, 后臺(tái)運(yùn)行的狀態(tài)是S.
僵尸進(jìn)程已經(jīng)死亡, 無法用kill殺死, 所以只能回收. 回收一個(gè)僵尸進(jìn)程可以調(diào)用wait()或者waitpid(), 也可以將其父進(jìn)程殺死后使其變?yōu)楣聝哼M(jìn)程, 由init領(lǐng)養(yǎng)后回收.
wait()
pid_t wait(int *status)
傳出參數(shù)status(配合宏)表示僵尸進(jìn)程的成因, 返回值為僵尸進(jìn)程pid. wait()函數(shù)可以清除PCB殘留信息, 使父進(jìn)程阻塞等待子進(jìn)程完成. 一次wait()調(diào)用只能回收一個(gè)子進(jìn)程.
// 當(dāng)子進(jìn)程正常結(jié)束時(shí)(如return 13或者 exit(13)), 可以獲取到退出信息13
if (WIFEXITED(status))
{
printf("child exit with %d", WEXITSTATUS(status));
}
// 當(dāng)程序異常退出時(shí)(接收到信號(hào)), 獲取中斷信號(hào)(如9, kill -9)
// 當(dāng)kill不加參數(shù)時(shí)信號(hào)默認(rèn)為15-SIGTERM, 另外段錯(cuò)誤是11, 可使用kill -l查看各種信號(hào)
if (WIFSIGNALED(status))
{
printf("child killed by %d", WTERMSIG(status));
}
if (WIFSTOPPED(status)
{
printf("child stopped by %d", WSTOPSIG(status));
}
if (WIFCONTINUED(status))
{
printf("child continued");
}
waitpid()
pid_t waitpid(pid_t pid, int* status, in options);
同wait, 但可以指定pid進(jìn)程清理, 也可以不阻塞(options參數(shù)使用WNOHANG輪詢模式), options為0則為阻塞.
fork的子進(jìn)程默認(rèn)跟父進(jìn)程是一個(gè)進(jìn)程組的, 所以如果父進(jìn)程調(diào)用waitpid()時(shí)第一個(gè)參數(shù)傳0和傳-1是一樣的. 父子進(jìn)程組ID默認(rèn)為父進(jìn)程的ID
如果第一個(gè)參數(shù)傳-xxxx就會(huì)把這一進(jìn)程組的子進(jìn)程都回收, 使用ps -ajx
可以查看到進(jìn)程組ID, 等價(jià)于kill -9 -xxxx(進(jìn)程組ID)
.
當(dāng)?shù)谌齻€(gè)參數(shù)為0時(shí), waitpid(-1, NULL, 0)等價(jià)于wait(NULL), 回收任意子進(jìn)程. 第三個(gè)參數(shù)傳WNOHANG時(shí)(需使用輪詢結(jié)構(gòu)), 非阻塞回收, 如果子進(jìn)程正在運(yùn)行函數(shù)返回0, 如果成功清理返回子進(jìn)程ID, 如果失敗(無子進(jìn)程)返回-1.
進(jìn)程組/會(huì)話
getpgrp()獲取當(dāng)前進(jìn)程的進(jìn)程組ID, getpgid獲取指定進(jìn)程的進(jìn)程組ID(傳0就是自身的). setpgid使某一進(jìn)程自立門戶, 成為新進(jìn)程組的組長.
會(huì)話的SID是創(chuàng)建該會(huì)話的領(lǐng)頭進(jìn)程的PID, 一般就是shell. Session的意義在于多個(gè)進(jìn)程組(job)在一個(gè)終端中運(yùn)行檬某,其中的一個(gè)為前臺(tái) job,它直接接收該終端的輸入并把結(jié)果輸出到該終端螟蝙。其它的 job 則在后臺(tái)運(yùn)行恢恼。
如果我們?cè)?session 中執(zhí)行了 nohup 等類似的命令,當(dāng) session 消亡時(shí)胶逢,相關(guān)的進(jìn)程并不會(huì)隨著 session 結(jié)束厅瞎,原因是這些進(jìn)程不再受 SIGHUP 信號(hào)的影響.
nohup java -jar app.jar >log 2>&1 &
最后一個(gè)&表示把條命令放到后臺(tái)執(zhí)行, 2>&1一定要寫到>log后面,才表示標(biāo)準(zhǔn)錯(cuò)誤輸出和標(biāo)準(zhǔn)輸出都重定向到log中.
本來1----->屏幕 (1指向屏幕)
執(zhí)行>log后, 1----->log (1指向log)
執(zhí)行2>&1后初坠, 2----->1 (2指向1,而1指向log,因此2也指向了log)
守護(hù)進(jìn)程
創(chuàng)建守護(hù)進(jìn)程: 1. 創(chuàng)建子進(jìn)程, fork() 2. 子進(jìn)程創(chuàng)建新會(huì)話,丟棄終端, setsid() 3. 移動(dòng)工作目錄到根目錄, chdir() 4. 改變umask掩碼, umask(0002) 5. 重定向012文件描述符到/dev/null(會(huì)話不需要終端), dup2() 6. 將1-5封裝到一個(gè)函數(shù)中來調(diào)用, 之后開始執(zhí)行守護(hù)進(jìn)程任務(wù)