寒假學習 第15天 (linux 進階編程) 筆記 總結
接著昨天
一、進程2.建立進程
(1) int system(const char *command);
(2) FILE *popen(const char *command, const char *type);
(3) exec系列函數
int execl( const char *path, const char *arg, ...);
//第一個參數:替換的進程,第二個參數..... 命令列
//命令列格式:命令名 選項參數 並且命令列必須、以0結尾
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[]);
替換當前進程的代碼空間的代碼資料,函數本生不會建立進程 見例子1
execl與execlp的區別
execl必須是絕對路徑,execlp不用 見例子2
例子1:
test.c
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ printf("%d\n",getpid()); sleep(5); return 0;}
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ printf("%d\n",getpid()); int r=execl("test","mytest",NULL);//結尾必須是0 printf("end %d\n",r); return 0;}
運行時 printf(“end %d\n”,r); 不會執行
並且列印的兩個pid相同
例子2:
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ printf("%d\n",getpid()); int r=execl("/bin/ls","ls","-l",NULL); printf("end %d\n",r); return 0;}
(4) pid_t fork(void);
1. 複製父進程的代碼跟執行位置,重fork開始就分兩個進程進行,例子1
2. fork建立的進程,父子進程是同時進行的
子進程建立好了以後,父進程馬上把時間片交給系統,然後系統在調度下一個時間片由那個執行。 例子2
例子1:
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ printf("before creat process!\n"); int pid=fork(); printf("after creat process:%d\n",pid); return 0;}結果:before creat process!after creat process:2651after creat process:0 //為什麼為0?因為fork不光複製父進程的代碼,還複製了父進程的執行位置,所以子進程中pid=fork() 是沒有執行的,pid為0.
例子2:
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ printf("before creat process!\n"); int pid=fork(); while(1) { if(pid==0){ printf("AAAAA\n"); sleep(1); }else{ printf("BBBBB\n"); sleep(1); } } return 0;}結果:AAAAA 跟BBBBB交叉出現
3.進程的應用
1. fork 的用處? 使用fork實現多任務(Unix本身是不支援多線程,有時就要用fork建立多進程)
2. 實現多任務的方式
1.線程
2.進程
3.訊號
4.非同步
5.進程池與線程池
例子:使用進程建立多任務(讓螢幕同時顯示時間跟隨機數)
#include <curses.h>#include <unistd.h>#include <stdlib.h>#include <time.h>WINDOW *wtime,*wnumb;int main(int argc, const char *argv[]){ initscr(); box(stdscr,0,0); wtime=derwin(stdscr,3,10,0,(COLS-10)); //右上方顯示時間 wnumb=derwin(stdscr,3,11,(LINES-3)/2,(COLS-11)/2); //中間顯示隨機數 box(wtime,0,0); box(wnumb,0,0); refresh(); wrefresh(wtime); wrefresh(wnumb); if(fork()){ //父進程 time_t tt; struct tm *t; while(1) { tt=time(0); t=localtime(&tt); mvwprintw(wtime,1,1,"%02d:%02d:%02d",t->tm_hour,t->tm_min,t->tm_sec); refresh(); wrefresh(wtime); wrefresh(wnumb); sleep(1); } }else{ //子進程 while(1){ mvwprintw(wnumb,1,1,"%8d",rand()%100000000); refresh(); wrefresh(wtime); wrefresh(wnumb); usleep(100000); } } endwin(); return 0;}
4.理解進程(1).父子進程的關係
是獨立的兩個進程,有各自的pid
從進程樹可以看出兩個獨立但不平行是父子節點關係(pstree可以查看進程樹)
(2) 父進程先結束
子進程就依託根進程init (孤兒進程)
理論上孤兒進程對系統沒有任何的危害。
子進程先結束
子進程會成為殭屍進程。
殭屍進程不佔用記憶體,CPU但在我們的進程任務管理樹上佔用一個節點。
殭屍進程會造成進程名額資源的浪費,所以要處理殭屍進程
(3). 殭屍進程的回收(使用wait回收)
例子:
#include <stdio.h>#include <stdlib.h>#include <sys/wait.h>int main(int argc, const char *argv[]){ if(fork()){ int status; printf("parent\n"); wait(&status); printf("%d\n",WEXITSTATUS(status)); sleep(1000); }else{ printf("child\n"); sleep(10); exit(34); } return 0;}
運行10秒後 子進程回收,不會變成殭屍進程,pstree查看看不到子進程
4. 父進程怎麼知道子進程退出
子進程結束通常會向父進程發送 一個SIGCHLD訊號(kill -l 可以查看所有訊號)
5.父進程處理子進程退出訊號
signal(int sig,void(*fun)(int));
向系統註冊:告訴系統只要sig訊號發生,系統就停止進程,並調用fun函數。
當函數執行完畢,繼續原來的進程 (中斷)
1 實現處理函數
2使用signal來綁定訊號與函數
例子:殭屍進程回收
#include <stdio.h>#include <stdlib.h>#include <sys/wait.h>void deal(int s){ int status; wait(&status); printf("回收中!...\n"); //這個函數不執行完畢程式就不會進行下去 sleep(10); printf("回收完畢:%d\n",WEXITSTATUS(status));}int main(int argc, const char *argv[]){ if(fork()){ signal(17,deal); //向系統註冊 SIGCHLD 就是 17 while(1) { printf("parent\n"); sleep(1); } }else{ printf("child\n"); sleep(10); printf("child out\n"); exit(34); } return 0;}
6. 父子進程的資源訪問 (1).記憶體資源1
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ int a = 20; if(fork()){ printf("patent a:%d\n",a); printf("patent a:%p\n",&a); a=33; sleep(3); }else{ printf("child a:%d\n",a); printf("child a:%p\n",&a); sleep(2); printf("child a:%d\n",a); printf("child a:%p\n",&a); } return 0;}結果:patent a:20patent a:0x7fffbd3e533cchild a:20child a:0x7fffbd3e533cchild a:20child a:0x7fffbd3e533c
#include <stdio.h>#include <unistd.h>int main(int argc, const char *argv[]){ int *a = (int *)malloc(4); *a = 20; if(fork()){ printf("patent a:%d\n",*a); printf("patent a:%p\n",a); a=33; sleep(3); }else{ printf("child a:%d\n",*a); printf("child a:%p\n",a); sleep(2); printf("child a:%d\n",*a); printf("child a:%p\n",a); } return 0;}結果:patent a:20patent a:0x1d2f010child a:20child a:0x1d2f010child a:20child a:0x1d2f010
由上面兩例子可得到
子進程複製了整個記憶體地區,但記憶體地區指向不同的物理空間(虛擬位址相同,但映射不同)
儘管複製,但記憶體獨立,不能互相訪問。
多進程實現多任務,進程之間的資料交換是個大問題Inter-Process Commucation
2
#include <stdio.h>#include <unistd.h>#include <sys/mman.h>int main(int argc, const char *argv[]){ int *a = (int *)mmap(0,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED,0,0); *a = 20; if(fork()){ printf("patent a:%d\n",*a); printf("patent a:%p\n",a); *a = 33; sleep(3); }else{ printf("child a:%d\n",*a); printf("child a:%p\n",a); sleep(2); printf("child a:%d\n",*a); printf("child a:%p\n",a); } return 0;}結果:patent a:20patent a:0x7f76f73f1000child a:20child a:0x7f76f73f1000child a:33 //改變了,child a:0x7f76f73f1000
#include <stdio.h>#include <unistd.h>#include <sys/mman.h>int main(int argc, const char *argv[]){ //跟上面的例子的區別只是把PROT_SHARE 改成MAP_PRIVATE int *a = (int *)mmap(0,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,0,0); *a = 20; if(fork()){ printf("patent a:%d\n",*a); printf("patent a:%p\n",a); *a = 33; sleep(3); }else{ printf("child a:%d\n",*a); printf("child a:%p\n",a); sleep(2); printf("child a:%d\n",*a); printf("child a:%p\n",a); } return 0;}結果:patent a:20patent a:0x7fe9bb8ab000child a:20child a:0x7fe9bb8ab000child a:20 //沒有改變child a:0x7fe9bb8ab000
映射記憶體
MAP_SHARED: 映射到同一實體記憶體
MAP_PRIVATE: 映射到不同的實體記憶體
#include <stdio.h>#include <unistd.h>#include <sys/mman.h>int main(int argc, const char *argv[]){ int *a=sbrk(4); *a = 20; if(fork()){ printf("patent a:%d\n",*a); printf("patent a:%p\n",a); *a = 33; sleep(3); }else{ printf("child a:%d\n",*a); printf("child a:%p\n",a); sleep(2); printf("child a:%d\n",*a); printf("child a:%p\n",a); } return 0;}結果:patent a:20patent a:0x12a1000child a:20child a:0x12a1000child a:20child a:0x12a1000
sbrk映射的是
MAP_PRIVATE的
(2).檔案資源
#include <stdio.h>#include <fcntl.h>int main(int argc, const char *argv[]){ int fd=open("test.txt",O_RDWR); if(fork()){ printf("patent\n"); char buf[1024]={0}; read(fd,buf,1024); printf("%s\n",buf); }else{ printf("child\n"); char buf[1024]={0}; read(fd,buf,1024); printf("%s\n",buf); } close(fd); return 0;}test.txt 內容123456結果:patent123456child
檔案是獨立的,close() ,每個進程都要有
為什麼子進程沒有內容?因為read中系統管理的指標移動了,兩個進程操作的是同一個核心對象
改成下面這樣就沒問題了
#include <stdio.h>#include <fcntl.h>int main(int argc, const char *argv[]){ int fd=open("test.txt",O_RDWR); if(fork()){ printf("patent\n"); char buf[1024]={0}; lseek(fd,0,SEEK_SET); read(fd,buf,1024); printf("%s\n",buf); }else{ printf("child\n"); char buf[1024]={0}; lseek(fd,0,SEEK_SET); read(fd,buf,1024); printf("%s\n",buf); } close(fd); return 0;}結果:patent123456child123456
#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <sys/mman.h>main(){ int fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666); if(fork()) { write(fd,"Hello",5); close(fd); }else{ write(fd,"Word",5); close(fd); }}cat test.txt結果:HelloWord
#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <sys/mman.h>main(){ if(fork()) { int fd=open("test.txt",O_RDWR); write(fd,"Hello",5); close(fd); }else{ int fd=open("test.txt",O_RDWR); write(fd,"Word",5); close(fd); }}cat test.txt 結果:Word
結論: 兩個進程之間,檔案描述付指向的是同一個檔案對象 進程的資料交換,居於兩種方式: 記憶體:有序/無序 mmap 檔案:有序/無序 普通檔案 由此可得出所有進程對象的通訊都是基於核心對象的:檔案,記憶體,隊列