linux下進程的控制
進程的創建
- 首先,我們知道進程的創建需要調用fork()函數。
- fork()一次調用兩次返回,子進程返回0,父進程返回子進程的pid。
- 同樣也可以調用vfork來創建子進程,但此時父子進程共享地址空間,因此我們一般不建議使用。
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid=fork();
if(pid<0)
{
perror("fork");
return -1;
}
if(pid==0)
{
//chilid
while(1)
{
printf("I am Child\n");
sleep(1);
}
}
while(1)
{
printf("I am Father\n");
sleep(1);
}
return 0;
}
這樣就完成了進程的創建。
進程的等待
我們知道,一個進程的退出,它的父進程必須知道它的退出信息,因此會保持殭屍狀態,而如果殭屍進程沒有人去回收的話,就會造成內存的泄露。
父進程需要通過等待的方式,來獲取子進程退出的信息,並且回收掉子進程的資源。
函數1:pid_t wait(int*status),成功返回等待進程id,失敗返回-1,並且帶出等待進程的退出信息。不關心退出信息的話就可以設置成NULL;
函數2:pid_t waitpid(pid_t pid,int *status,int options);正常返回收集到的子進程的ID,如何設置了WNOHANG,調用發現已經沒有已退出的子進程時候,返回0,pid=-1時,表示回收任一個子進程,pid>0表示等待其進程的ID和pid相等的子進程。
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 int status=0;
6 int count=5;
7 pid_t pid=fork();
8 if(pid<0)
9 {
10 perror("fork");
11 return -1;
12 }
13 else if(pid==0)
14 {
15 //child
16 while(count--)
17 {
18 printf("Child process running\n");
19
20 }
21 exit(1);
22
23
24 }
25 else
26 {
27 //father
28 wait(&status);
29 printf("status is %d\n",status);
30 printf("Father process running\n");
31 }
32
33 return 0;
34 }
其中不管是wait還是waitpid,都有一個status標誌位標誌着子進程的退出消息,因此很有必要研究一下里面到底存放的是什麼。
- 首先,這個status不能簡單的當,作一個整形來看待,更應該把它比喻成一個位圖。
- 而我們只需要研究它的低16位就足夠了
- 其中,當一個進程代碼完成,正常結束掉的時候,它所告訴父進程的退出狀態存放在高8位。第八位全0
- 而它如果是由信號殺死的話,那麼它的低7位就存放的是被那個終止信號幹掉的,第8爲是core dump 標誌位,表示它是否生成了core dump文件。我們稱之爲核心轉儲
進程終止
- 一個進程的結束有三種情況:1.代碼運行完,結果正確,2.代碼運行完,結果不正確,3.代碼
運行異常終止 - 而正常退出又分爲man函數return返回和調用exit或者_exit來。
- 異常終止比如接收到ctrl+c 這樣的信號終止。
調用exit
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6
7 printf("hello world\n");
8 exit(1);
9 }
調用_exit
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6
7 printf("hello world");
8 _exit(1);
9 }
- 爲什麼同樣是正常退出,但是結果卻不一樣呢,這裏我們要講一下exit和_exit的區別。
- 首先,exit是c庫函數,而_exit是系統調用函數,exit封裝了_exit。
- 當我們調用exit 的時候,首先執行用戶自定義的清理函數,再刷新緩衝區,關閉流等操作,最後纔回去調用_exit。
- 其中,還有一種退出方式就是return ,其實return還是將後面的參數傳遞給了exit。
進程替換
- 我們創建子進程的最主要原因,就是想讓子進程來代替父進程做一部分工作。而fork出來的子進程和父進程擁有相同的代碼和數據,因此就需要對子進程的代碼和數據進行替換,從而完成別的任務,這就是進程替換的基本思想。
- 下面我們來看看6個進程替換的函數:
如何更好記憶
- 首先,execve纔是真正的系統調用接口,這些函數都是在它的基礎上進行了封裝.
- l表示參數使用列表,即可變參數參數列表注意要以NULL結尾,而v表示參數採用數組,需要用戶自己定義。
- 帶p表示自動搜索當前環境變量PATH,不帶P就需要手動的來添加路徑。
- 帶e表示需要自己維護環境變量。
自己實現一個shall
綜合了之前所學的知識,我們大概能明白shall是如何運行命令的,當我們用戶輸入一條命令時,shall獲取這條命令,並且對它進行解析,創建一個子進程,然後用當前命令替換子進程,父進程等待子進程的退出。
1 #include<stdio.h>
2 #include<sys/wait.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<unistd.h>
6 char *argv[8];
7 int argc=0;
8 void do_pause(char*buf)
9 {
10 int i;
11 int status=0;
12 for(argc=i=0;buf[i];i++)
13 {
14 if(!isspace(buf[i])&&status==0)
15 {
16 argv[argc++]=buf+i;
17 status=1;
18
19 }
20 else if(isspace(buf[i]))
21 {
22 status=0;
23 buf[i]=0;
24 }
25 }
26 argv[argc]=NULL;
27 }
28 void do_execute(void)
29 {
30 pid_t pid=fork();
31 switch(pid)
32 {
33 case -1:
34 perror("fork");
35 exit(EXIT_FAILURE);
36 break;
37 case 0:
38 execvp(argv[0],argv);
39 perror("execvp");
40 exit(EXIT_FAILURE);
41 default:
42 {
43 int st;
44 while(wait(&st)!=pid)
45 ;
46 }
47 }
48 }
49 int main()
50 {
51 char buf[1024]={};
52 while(1)
53 {
54 printf("myshell>>");
55 scanf("%[^\n]%*c",buf);
56 do_pause(buf);
57 do_execute();
58
59 }
60 }
popen/system
- FILE *popen(const char *command, const char *type);
- 這個函數創建一個管道,然後通過fork或者inovke一個子進程,然後在子進程裏面執行command的命令,然後把結果返回到標準I/O當中,由於管道只能單向同通信的原因,所以command只能從管道中讀取r或者把結果w到管道里面。在調用完成後我們要調用fclose來回收創建的子進程,不然就可能產生殭屍進程。
int system(const char *command); - 這個函數創建一個進程,然後進程替換去執行我們指定的command命令。然後shell阻塞直到該命令執行完成,再返回到調用函數,
- 函數 process_create(pid_t* pid, void* func, void* arg), func回調函數就是子進程執行的入口函數, arg是傳遞給func回調函數的參數
int process_create(int(*func)(),const char* file,char* argv[])
{
int ret = 0;
pid_t pid = fork();
if(pid == 0)
{
//child
ret = func(file,argv);
if(ret == -1)
{
printf("調用execvp失敗\n");
perror("func");
_exit(-1);
}
}
else
{
int st;
pid_t ret = wait(&st);
if(ret == -1)
{
perror("wait");
exit(-1);
}
}
return 0;
}
int main()
{
char* argv1[] = {"ls"};
char* argv2[] = {"ls","-al","/etc/passwd",0};
process_create(execvp,*argv1,argv2);
return 0;
}