文章目錄
- 1.1 system
- 1.2 popen:建立子進程
- 1.3 exec系列函數
- 1.4 fork
- 6.2 記憶體映射與子進程:
- 6.3 檔案描述符的拷貝
1什麼是進程:進程是一個執行中的程式
執行的程式: 代碼->資源->CPU
進程有很多資料維護:進程狀態/進程屬性
所有進程屬性採用的一個樹形結構體維護
ps -a//所有進程
ps -aue //有效進程
進程狀態:(man ps)
D Uninterruptible sleep (usually IO)
R Running or runnable (on run queue)
S Interruptible sleep (waiting for an event to complete)
T Stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z Defunct ("zombie") process, terminated but not reaped by its parent.
For BSD formats and when the stat keyword is used, additional characters may be
displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 多進程
+ is in the foreground process group 前台進程
top 即時查看進程
pstree 查看進程樹
kill 向進程發送訊號 kill -s 訊號 進程ID
kill -l //查看所有訊號
kill -s 9 224 //關閉224進程
2 建立進程1 進程有關的建立函數1.1 system
int system(const char*filename);
建立一個獨立的進程,擁有獨立的代碼空間
等待新的進程執行完畢,system才返回(阻塞)
使用system調用函數(觀察進程ID 觀察阻塞)
新進程的傳回值和system傳回值有關係
1任何進程的傳回值不要超過255
2 system中進程的傳回值存放在system傳回值的8-15位
可以通過printf("%d\n",r>>8 ); 這樣來獲得傳回值
3 linux中有專用的獲得返回狀態的宏
WEXITSTATUS(status) //#include<wait.h> #include <sys/wait.h>
sys.c
#include <stdio.h>#include <unistd.h>int main(){printf("%d\n",getpid());sleep(2);return 254;}sys2.c#include <stdio.h>#include <unistd.h>#include <wait.h>#include <sys/types.h>int main(){int r;printf("%d\n",getpid());r = system("./sleep");//printf("%d\n",r>>8 );printf("%d\n",WEXITSTATUS(r));}
gcc sys.c -o sleep
gcc sys2.c
./a.out
1.2 popen:建立子進程
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
案例:使用popen調用ls -l 並且建立一個管道讀取輸出
#include <stdio.h>#include <unistd.h>int main(){FILE *fp = popen("cat file","r"); //注意這裡是讀取輸出,不是開啟檔案 不能直接寫檔案名稱//FILE *fp = popen("ls -l","r");if(!fp){perror("popen");return 1;}int fd = fileno(fp);int r;char buf[1024] = {0};while((r=read(fd,buf,1023)) > 0){buf[r] = 0;printf("%s",buf);}pclose(fp);}
1.3 exec系列函數
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[]);
作用: 替換當前代碼空間中的資料
函數本身不建立新的進程
第一個參數:替換的程式
第二個參數:
命令列
命令列格式:命令名 選項參數 NULL
命令列結尾必須是Null 字元串
execl execlp的區別:
execl 只在當前路徑搜尋
execlp 可以使用系統搜尋路徑(which能找到)
如果都找不到,可以使用絕對路徑
命令鎖指向的程式 命令本身 參數列表
如果返回-1 失敗
#include <stdio.h>int main(){int r = execl("/bin/ls","ls","-l",0); // 只能調用當前路徑//int r = execlp("ls","ls","-l",0);// 能調用系統路徑 printf("調用結束%d\n",r);return 0;}
1.4 fork
pid_t fork()
1 建立進程
2 新進程的代碼是什麼:複製父進程的挨罵
而且複製來執行的位置
3 在子進程中不調用fork 所有傳回值=0
4 父子進程同時執行
#include <stdio.h>#include <unistd.h>int main(){printf("建立進程前\n");int pid = fork(); //父進程執行的while(1){if(pid == 0){printf("子進程 %d\n",getpid());}else{printf("父進程 %d\n",getpid());}}}
3 應用進程
使用fork實現多任務(unix本身是不支援線程的)
1 進程
2 線程
3 訊號
4 非同步
5 進程池與線程池
4 理解進程
1父子進程的關係
獨立的兩個進程
互為父子關係
使用pstree看到
├─gnome-terminal─┬─bash───a.out───a.out //父子關係
│ ├─bash───pstree
│ ├─gnome-pty-helpe
│ └─2*[{gnome-terminal}]
2 問題:
1如果父進程先結束 子進程在麼辦
子進程將變成孤兒進程,直接依託根進程(init)
孤兒 進程是沒有危害的
init─┬─NetworkManager─┬─dhclient
│ └─{NetworkManager}
├─a.out
2 如果子進程先結束 父進程怎麼辦
子進程先結束,子進程會成為殭屍進程。
殭屍進程的特點: 不佔用記憶體 cpu,但在進程任務管理樹上佔用一個節點(寶貴)
實際上殭屍進程會造成進程名額的資源浪費。一定要處理殭屍進程
├─gnome-terminal─┬─bash───pstree
│ ├─bash───a.out───a.out
3 殭屍進程使用wait回收(阻塞函數)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait 阻塞直到任意進程結束,status來接收進程傳回值,傳回值表示返回的進程號
waitpid 阻塞直到指定進程結束
WEXITSTATUS(status) 解析傳回值 status的 8-15位是進程的傳回值
4 父進程怎麼知道子進程退出?
子進程結束時,通常會向父進程發送一個SIGCHLD訊號
5父進程處理子進程的訊號
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal 的功能 :
向系統註冊,只要接收到訊號signal,系統停止進程,執行handler函數,
當函數執行完畢,繼續原來的進程 (非強制中斷)
5.1實現處理函數
5.2 使用signal綁定訊號與函數
只有當子進程退出時才用wait,因為wait是一個阻塞函數。所以wait和signal一起用。
#include <stdio.h>#include <unistd.h>#include <sys/wait.h>#include <sys/types.h>#include <signal.h>void deal(int s){printf("回收中\n");sleep(5);int status;wait(&status);printf("回收完畢%d\n",WEXITSTATUS(status));}int main(){printf("建立進程前\n");int pid = fork(); //父進程執行的if(pid == 0){printf("子進程 %d\n",getpid());sleep(5);return 88;}else{printf("父進程 %d\n",getpid());signal(SIGCHLD,deal);while(1){sleep(1);printf("parent\n");}return 0;}}
zhao@ubuntu:~/unix/5$ ./a.out
建立進程前
父進程 2324
子進程 2325
parent
parent
parent
parent
回收中
回收完畢88
parent
parent
parent
^C
6 父子進程的記憶體空間
6.1 全域變數 局部變數 堆變數 都會被子進程拷貝,但與原先的獨立。
注意 堆記憶體被複製了,需要分別在各個進程中手動釋放。
子進程複製了父進程的全域區和局部區記憶體,但記憶體地區指向不同的物理空間。
儘管複製但記憶體獨立,不能相互訪問。
處理序間通訊(IPC)是大問題。
6.2 記憶體映射與子進程:
記憶體映射的屬性,決定子進程和父進程是否映射在同一物理空間。
MAP_SHARED: 映射到同一物理空間。 (改一個進程中的,其他進程的也變化)
MAP_PRIVATE:映射到不同的物理空間。
#include <stdio.h>#include <unistd.h>#include <sys/mman.h>int main(){int *a = mmap(0,4,PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED,0,0);*a = 20;int pid = fork(); //父進程執行的if(pid == 0){*a= 90;printf("parent :a=%d\n",*a);//90}else{sleep(3);printf("child :a=%d\n",*a);//90}}
因為使用的是MAP_SHARED ,所以射到了同一物理空間, 改動會影響其它的.
若使用MAP_PRIVATE 就可以映射到不同物理空間.
6.3 檔案描述符的拷貝
每個進程都維護一個檔案描述符列表。
父子進程間,拷貝了檔案描述符, 相同檔案描述符指向的是同一個檔案核心對象。
1 各個進程的檔案描述符都需要close。
2 對檔案的讀寫會改變檔案對象的讀寫指標位置,對所有進程都有影響。
#include <stdio.h>#include <unistd.h>#include <sys/mman.h>#include <fcntl.h>int main(){int fd = open("test.txt",O_RDWR);int pid = fork(); //父進程執行的if(pid == 0){printf("parent:\n");char buf[1024] ={0};lseek(fd,0,SEEK_SET);read(fd,buf,1023);printf("%s\n",buf);close(fd);}else{printf("child:\n");char buf[1024] ={0};lseek(fd,0,SEEK_SET);read(fd,buf,1023);printf("%s\n",buf);close(fd);}}
進程的資料交換,基於兩種方式:
記憶體: 有序/無序:mmap
檔案:有序/無序:普通檔案
基於核心對象:檔案/記憶體/隊列