廈門廣告公司有哪些aso優(yōu)化的主要內(nèi)容為
Linux進(jìn)程控制(2)
📟作者主頁(yè):慢熱的陜西人
🌴專欄鏈接:Linux
📣歡迎各位大佬👍點(diǎn)贊🔥關(guān)注🚓收藏,🍉留言
本博客主要內(nèi)容講解了進(jìn)程等待收尾內(nèi)容和進(jìn)程的程序替換,以及進(jìn)程程序替換的原理,進(jìn)程程序替換的7個(gè)重要接口
文章目錄
- Linux進(jìn)程控制(2)
- 1.進(jìn)程等待(續(xù))
- 2.進(jìn)程程序替換
- 2.1 程序替換是如何完成的---單線程版
- 2.2程序替換的原理
- 2.3引入多進(jìn)程,使用所有程序替換的接口
- 熟悉所有的替換程序接口(7個(gè))
1.進(jìn)程等待(續(xù))
我們稍微改造一下,之前進(jìn)程等待的時(shí)候,父進(jìn)程不要阻塞等待的代碼,讓父進(jìn)程真正的去運(yùn)行一些任務(wù)。
我們采用函數(shù)回調(diào)的方式,讓父進(jìn)程在等待子進(jìn)程的時(shí)候也可以去運(yùn)行自己的一些任務(wù)!
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>#define TASK_NUM 10//預(yù)設(shè)一批任務(wù)
void sync_disk()
{printf("這是一個(gè)刷新數(shù)據(jù)的任務(wù)\n");
}void sync_log()
{printf("這是一個(gè)同步日志的任務(wù)\n");
}void net_send()
{printf("這是一個(gè)網(wǎng)絡(luò)發(fā)送的任務(wù)\n");
} //保存相關(guān)的任務(wù)
typedef void (*func_t)(); //定義了一個(gè)函數(shù)指針類型
func_t orther_task[TASK_NUM] = {NULL}; //裝載任務(wù)
int Load_Task(func_t fuc)
{int i = 0;for(; i < TASK_NUM; ++i){if(orther_task[i] == NULL) break;}if(TASK_NUM == i) return -1;else orther_task[i] = fuc;return 0;
}//初始化函數(shù)指針數(shù)組
void Init_Task()
{for(int i = 0; i < TASK_NUM; ++i) orther_task[i] = NULL;Load_Task(sync_disk); Load_Task(sync_log);Load_Task(net_send);
}void Run_Task()
{for(int i = 0; i < TASK_NUM; ++i){if(orther_task[i] == NULL) continue;else orther_task[i]();}
}int main()
{pid_t id = fork();if(id == 0){//子進(jìn)程int cnt = 5;while(cnt){printf("我是子進(jìn)程,我還活著呢,我還有%dS,我的pid:%d,我的ppid:%d\n", cnt--, getpid(), getppid());sleep(1);}exit(0);}Init_Task();while(1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG);// 夯住了if(ret_id < 0){printf("waitpid_error\n");}else if(ret_id == 0){Run_Task(); sleep(1);continue;}else{printf("我是父進(jìn)程,我等待成功了,我的pid:%d,我的ppid:%d, ret_id: %d, child exit code: %d, child exit signal:%d\n",getpid(), getppid(), ret_id, (status >> 8)&0xFF, status & 0x7F);exit(0);}sleep(1); }return 0;
}
運(yùn)行結(jié)果:
繼續(xù)改進(jìn),我們之前獲取進(jìn)程退出碼的時(shí)候是使用(status >> 8)& 0xFF
的方式來(lái)進(jìn)行獲取的,那么實(shí)際上C庫(kù)也給我們提供了兩個(gè)宏來(lái)幫助我們獲取進(jìn)程的退出碼:
status:
WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的退出碼)
我們用這兩個(gè)宏來(lái)優(yōu)化一下我們等待成功,也就是子進(jìn)程結(jié)束的時(shí)候的代碼:
else { //等待成功 if(WIFEXITED(status)) { //正常退出 printf("wait success, child exit code :%d", WEXITSTATUS(status)); } else { //異常退出 printf("wait success, child exit signal :%d", status & 0x7F); } exit(0); }
正常退出:
異常退出:我們嘗試在父進(jìn)程等待的時(shí)候殺掉子進(jìn)程:
2.進(jìn)程程序替換
我們?yōu)槭裁葱枰獎(jiǎng)?chuàng)建子進(jìn)程?為了讓子進(jìn)程幫我執(zhí)行特定的任務(wù);
①讓子進(jìn)程執(zhí)行父進(jìn)程的一部分代碼;
②如果子進(jìn)程指向一段全新的代碼呢?這時(shí)候我們就需要進(jìn)程的程序替換!
也是為什么需要進(jìn)程的程序替換。
2.1 程序替換是如何完成的—單線程版
代碼:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> int main()
{ printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); printf("begin......\n"); execl("/bin/ls", "ls", "-a", "-l", NULL); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n"); return 0;
}
運(yùn)行結(jié)果:
那么我們可以看到這個(gè)進(jìn)程運(yùn)行了開始的begin....
,然后運(yùn)行了ls
,但是后面的end...
卻不見(jiàn)了。
這是因?yàn)榘l(fā)生了進(jìn)程的程序的替換,簡(jiǎn)要的原理就是,操作系統(tǒng)通過(guò)提供的地址/bin/ls
從磁盤中拿出ls
然后選到指定的文件ls
,在輸入一些參數(shù)-a, -l
,以NULL
表示結(jié)束。
2.2程序替換的原理
操作系統(tǒng)不動(dòng)當(dāng)前進(jìn)程的內(nèi)核數(shù)據(jù)結(jié)構(gòu),而是去磁盤內(nèi)部拿到要替換的數(shù)據(jù)和代碼,將我們當(dāng)前進(jìn)程的數(shù)據(jù)和代碼替換掉。
當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程開始執(zhí)行。
所以進(jìn)程的程序替換,是沒(méi)有創(chuàng)建新的進(jìn)程的。
①站在進(jìn)程的角度
操作系統(tǒng),幫我們?cè)诖疟P內(nèi)部找到我們要替換的數(shù)據(jù)和代碼,替換進(jìn)程的數(shù)據(jù)和代碼。
②站在程序的角度
這個(gè)程序被加載了,所以我們也稱execl
這類函數(shù)為加載器
。
我們?cè)诨氐揭婚_始,為什么我們程序后面的end.....
卻沒(méi)有打印出來(lái)?
原因是當(dāng)我們加載程序替換的時(shí)候,新的數(shù)據(jù)和代碼就進(jìn)入了進(jìn)程,當(dāng)前進(jìn)程后續(xù)沒(méi)有沒(méi)運(yùn)行的代碼就成為了老代碼,直接被替換了,沒(méi)有機(jī)會(huì)執(zhí)行了。
所以進(jìn)程的程序替換是整體替換,而不是局部替換。
所以我們接下來(lái)引入多進(jìn)程的程序替換
2.3引入多進(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 printf("我是子進(jìn)程:%d", getpid()); execl("/bin/ls", "ls", "-a", "-l", NULL); } sleep(2); //father waitpid(id, NULL, 0); printf("我是父進(jìn)程:%d\n", getpid()); return 0;
}
運(yùn)行結(jié)果:
我們看到execl
之后父進(jìn)程的內(nèi)容也被運(yùn)行了
因?yàn)檫M(jìn)程的獨(dú)立性,所以進(jìn)程的程序替換只會(huì)影響調(diào)用程序替換的子進(jìn)程。
子進(jìn)程加載新程序的時(shí)候,是需要進(jìn)行程序替換的,發(fā)生寫時(shí)拷貝(子進(jìn)程執(zhí)行的可是全新的代碼啊,新的代碼,所以代碼區(qū)也可以發(fā)生寫時(shí)拷貝)
那么對(duì)于execl
這類加載函數(shù),它有沒(méi)有返回值呢?
答案是分情況:
①替換成功是沒(méi)有返回值的
②替換失敗是有返回值的
-1
原因是:假設(shè)替換替換成功了,那么我們?cè)撨M(jìn)程中的代碼和數(shù)據(jù),都會(huì)被替換成新的代碼和數(shù)據(jù),那么我們之前的返回值也就不復(fù)存在了,并且我們也不需要返回值了。
替換失敗的情況下,進(jìn)程之前的代碼和數(shù)據(jù)還是存在的,那么我們的返回值也是存在的,從而可以返回。
所以我們調(diào)用了加載函數(shù)之后我們不用去判斷它是否加載成功,只需要在函數(shù)后面返回異常即可。
失敗的例程:
#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 printf("我是子進(jìn)程:%d\n", getpid()); execl("/bin/lsss", "lsss", "-a", "-l", NULL); exit(1); } sleep(2); //father int status; waitpid(id,&status, 0); printf("我是父進(jìn)程:%d, child exit signal:%d\n", getpid(), WEXITSTATUS(status)); return 0;
}
熟悉所有的替換程序接口(7個(gè))
①int execl(const char *path, const char *arg, ...);
例如:
execl("/bin/ls", "ls", "-a", "-l", NULL);
l
代表list;
path
:路徑,也就是告訴操作系統(tǒng),你要用來(lái)替換的程序,在磁盤的哪個(gè)路徑,例程里的/bin/ls
;
arg
:文件,是你要用來(lái)替換的文件名,例程里面的ls
。其中的
...
是可變參數(shù)列表,例程中的那些參數(shù)-a , -l
。其中最后我們要特別的輸入一個(gè)
NULL
參數(shù),告訴函數(shù)參數(shù)結(jié)束了;
②int execv(const char *path, char *const argv[]);
相比較第一個(gè)函數(shù)的差別就是,第一個(gè)函數(shù)要求我們一個(gè)一個(gè)的去傳參數(shù),而第二個(gè)要求我們直接用數(shù)組的形式去傳,但是原則也是一樣的,數(shù)組的最后一個(gè)元素也要置成
NULL
。我們?cè)谧舆M(jìn)程內(nèi)部調(diào)用的時(shí)候是這樣的:
先創(chuàng)建一個(gè)數(shù)組,將這些參數(shù)一個(gè)一個(gè)放進(jìn)去,再傳給
execv
即可那么其實(shí)
v
就是vector
就是數(shù)組的意思;char* const argv[] = { "ls", "-a", "-l", NULL }; // execl("/bin/lsss", "lsss", "-a", "-l", NULL); execv("/bin/ls", argv); exit(1); }
運(yùn)行結(jié)果:
③int execlp(const char *file, const char *arg, ...);
p
:當(dāng)我們指定執(zhí)行程序的時(shí)候,只需要指定程序名即可,系統(tǒng)會(huì)自動(dòng)在**環(huán)境變量PATH
**中查找。也就是說(shuō)我們要用于替換的程序,必須在環(huán)境變量
PATH
中,或者說(shuō)我們?cè)诃h(huán)境變量PATH
中設(shè)置過(guò);execlp("ls", "ls", "-a", "-l", NULL);
那么其中的兩個(gè)
ls
是不一樣的,一個(gè)是文件名,一個(gè)是參數(shù)。
④int execvp(const char *file, char *const argv[]);
v
:表示參數(shù)以數(shù)組的形式傳入;
p
:表示在環(huán)境變量PATH
中去尋找用于替換的文件;char* const argv[] = { "ls", "-a", "-l", NULL }; execvp("ls", argv);
運(yùn)行結(jié)果:
⑤int execle(const char *path, const char *arg, ..., char * const envp[]);
envp[]
:叫做自定義環(huán)境變量,當(dāng)我們不想使用系統(tǒng)默認(rèn)的環(huán)境變量的時(shí)候,這個(gè)時(shí)候我們就傳遞一個(gè)envp
比如我們現(xiàn)在要讓我們的調(diào)用
exec
目錄下的ortherproc
來(lái)替換myproc
的子進(jìn)程的后續(xù)代碼
先用
execl
嘗試一下:execl("./exec/ortherproc", "ortherproc", NULL);
運(yùn)行結(jié)果:
換成的動(dòng)態(tài)的效果再看看:
下來(lái)我們嘗試用
execle
來(lái)實(shí)現(xiàn)一下:
proc.c
char* const envp[] = { "MYENVP=UCanCMe!", NULL }; execle("./exec/ortherproc", "ortherproc",envp);
ortherproc.cc
for(int i = 0; i < 5; ++i) { cout << "我是另一個(gè)程序,我的PID是 :" << getpid() << endl; cout << "MYENVP: " << (getenv("MYENVP")==NULL ? "NULL" : getenv("MYENVP")) << endl; cout << "PATH: " << (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl; sleep(1); }
運(yùn)行結(jié)果:
我們看到自定環(huán)境變量打印出來(lái)了,但是操作系統(tǒng)內(nèi)部的環(huán)境變量卻不見(jiàn)了,所以我們可以得到一個(gè)結(jié)論:
自定義環(huán)境變量覆蓋了,默認(rèn)的環(huán)境變量;
我們傳默認(rèn)的環(huán)境變量試試:
extern char ** environ;execle("./exec/ortherproc", "ortherproc",NULL , environ);
運(yùn)行結(jié)果:
那么如果我們兩個(gè)都要呢,那么有一個(gè)接口
putenv
給我們提供了一個(gè)將自定義環(huán)境變量追加到進(jìn)程的默認(rèn)環(huán)境變量的方法:接下來(lái)我們嘗試一下putenv("MYENVP=UCanCMe"); execle("./exec/ortherproc", "ortherproc", NULL, environ);
運(yùn)行結(jié)果:
插播一段:
我們知道環(huán)境變量具有全局屬性,可以被子進(jìn)程繼承下去,那么操作系統(tǒng)是怎么辦到的?
只需要用
execle
的最后一個(gè)參數(shù)傳過(guò)去即可!那么我們是不是不需要
putenv
也能實(shí)現(xiàn)兩個(gè)都能被子進(jìn)程讀取到呢?我們直接把自定義的環(huán)境變量
export
到bash
中試試:[mi@lavm-5wklnbmaja lesson6]$ export MYENVP=UCanCMe [mi@lavm-5wklnbmaja lesson6]$ echo $MYENVP UCanCMe
運(yùn)行結(jié)果:
我們發(fā)現(xiàn)是可行的,自定義環(huán)境變量-----> bash ----->父進(jìn)程------>子進(jìn)程
⑥int execvpe(const char *file, char *const argv[], char *const envp[]);
p
:不需要指定路徑,只要在環(huán)境變量?jī)?nèi)部即可;
v
:參數(shù)以數(shù)組的形式傳入;
e
:環(huán)境變量數(shù)組傳入;使用方法都與上面的類似。
⑦int execve(const char *filename, char *const argv[], char *const envp[]);
這個(gè)接口也不用過(guò)多介紹了,使用方法都是一樣的。
那么我們需要注意的是,在linux的man手冊(cè)中將區(qū)域六個(gè)接口都放在了
3
號(hào)手冊(cè),唯獨(dú)這個(gè)卻放在了2
號(hào)手冊(cè)。其實(shí)操作系統(tǒng)只給我們提供了一個(gè)程序替換的接口
execve
,剩下的幾個(gè)接口都是由這個(gè)接口封裝出來(lái)的。
并且我們程序替換的時(shí)候不僅可以替換C語(yǔ)言的,甚至其他的語(yǔ)言都可以替換,我上面的例子也做到了用C++替換,因?yàn)檫@些代碼都是交給操作系統(tǒng)來(lái)處理的而不是編譯器,所以不論是什么語(yǔ)言都是可以替換的!
到這本篇博客的內(nèi)容就到此結(jié)束了。
如果覺(jué)得本篇博客內(nèi)容對(duì)你有所幫助的話,可以點(diǎn)贊,收藏,順便關(guān)注一下!
如果文章內(nèi)容有錯(cuò)誤,歡迎在評(píng)論區(qū)指正