文件描述符
文件描述符表是一個指針數組,文件描述符是一個整數。
文件描述符表對應的指針是一個結構體,名字為file_struct
,里面保存的是已經打開文件的信息
需要注意的是父子進程之間讀時共享,寫時復制的原則是針對物理地址而言的,通過程序操控虛擬地址的我們是無法查別到這個問題的,其中的機理由MMU進行控制(詳見我的上一篇博客:Linux系統【一】CPU+MMU+fork函數創建進程)
測試程序:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{int *t = (int*)malloc(sizeof(int));*t = 0;printf("parent process:&t=%p\n",t);pid_t pid;pid = fork();if(0 == pid){printf("child process:&t=%p\n",t);(*t)++;printf("child process:&t=%p\n",t);printf("child process:t=%d\n",*t);}else{wait(NULL);printf("parent process:&t=%p\n",t);printf("parent process:t=%d\n",*t);}return 0;
}
運行結果:
exec函數族
fork創建子進程后執行的是和父進程相同的程序(有可能執行不同的代碼分支),子進程往往要調用一種exec
函數來執行另一個程序,當進程調用一種exec
函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程(簡單來講就是main
函數,實際上是一個由匯編和C混合編寫的程序)。調用exec
并不創建新進程,所以調用exec
前后進程的id
并未改變
命令行執行程序的實質其實就是fork+exec
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
//第一個參數為可執行文件的文件名,后面的參數是命令行參數/* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
- l list 命令行參數列表
- p PATH 搜索file時使用PATH變量
- v vector 使用命令行參數數組
- e environment 使用環境變量數組,不適用進程原有的環境變量,設置新加載程序運行的環境變量
execlp
調用程序的時候會搜索一遍環境變量,如果在環境變量中可以找到程序就會執行,否則按照路徑運行程序。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{pid_t pid;pid = fork();if(-1 == pid){perror("fork error:");exit(1);}else if(0 == pid){execlp("ls","ls","-l","-a",(char *)NULL);}else{sleep(1);printf("Parent");}return 0;
}
這里解釋一下上述程序:execlp
函數第一個參數是調用的程序,從第二個參數開始是命令行參數,最后一定要以(char*)NULL
結尾才能正常運行。其中比較特殊的是命令行參數的第一個參數是什么不重要,我上面雖然寫的是ls
,但其實隨便寫什么都可以。
為什么會有這種寫什么都可以的情況出現呢?實際原因是:我們在執行程序的時候系統會默認帶有一個參數(雖然這個參數大多數情況下都與程序名相同,但是某些shell會將此參數設置為完全的路徑名),所以我們第一個參數是不會有作用的,真正有作用的參數是從參數數組中第一個元素開始的。
例如:
//test.c
#include<stdio.h>int main(int argc,char* args[])
{for(int i=0;i<=argc;++i){printf("%d=[%s]\n",i,args[i]);}return 0;
}
將上面兩個程序組合:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{pid_t pid;pid = fork();if(-1 == pid){perror("fork error:");exit(1);}else if(0 == pid){execlp("./test","first","second","third",(char *)NULL);printf("Child\n");}else{sleep(1);printf("Parent\n");}return 0;
}
我們可以發現一旦運行execlp
將不會再運行原來進程后面的代碼,原本進程的代碼塊完全被新的文件代替
execl
不會查找環境變量,直接按照路徑運行程序
execle
需要借助環境變量表char ** environ
execv
傳入的需要是一個字符串數組,而不能直接傳命令行參數
例題:將當前系統中的進程信息打印到文件中
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main(int argc,char *argv[])
{if(argc != 2){printf("you should input filename\n");exit(1);}freopen(argv[1],"w",stdout);execlp("ps","ps","aux",(char*)NULL);return 0;
}
更好的做法是使用dup2
函數(其中的2
的意思是to
,如果后面是4
的話意思常常是for
),并將新進程放在子進程中
dep2(old fd,new fd); // 將舊文件描述符中的內容拷貝到新文件描述符中
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(int argc,char *argv[])
{if(argc != 2){printf("you should input filename\n");exit(1);}
// freopen(argv[1],"w",stdout);int fd = open(argv[1],O_CREAT | O_RDWR,06444);if(-1 == fd){perror("openfile error:");exit(1);}dup2(fd,STDOUT_FILENO);pid_t pid=fork();if(-1 == pid){perror("fork error:");exit(1);}else if(0 == pid){execlp("ps","ps","aux",(char*)NULL);perror("exec error:"); //不需要判斷返回值,如果成功不會運行下面的語句exit(1);}else{close(fd);}return 0;
}
需要注意的是使用dup2
函數以后原本標準輸出文件指針丟失了,如果還要回到標準輸出文件,應該之前用dup
保存一份,使用之后再dup2
回去就可以了。
exec
函數族沒有成功返回值,只有失敗返回值