Linux进程编程(PS: exec族函数、system、popen函数)
目錄
- 1.進程相關(guān)概念
- 程序和進程
- 查看系統(tǒng)中的進程
- ps指令
- top指令
- 進程標識符 使用getpid()獲取
- 父進程,子進程
- 2.創(chuàng)建進程fork
- 進程創(chuàng)建發(fā)生了什么——C程序的存儲空間如何分配
- 3.創(chuàng)建進程vfork(區(qū)別fork)
- 4.進程退出
- 正常退出
- 異常退出
- 5.父進程等待子進程退出
- 父進程收集子進程退出狀態(tài)(僵尸進程)
- 等待退出函數(shù)wait()
- 等待退出函數(shù)waitpid()
- 父進程先于子進程退出(孤兒進程)
- 6.exec族函數(shù)(讓子進程調(diào)用其他程序)
- execl,execlp
- execv execvp
- exec配合fork使用
- system函數(shù)
- popen函數(shù)
1.進程相關(guān)概念
程序和進程
程序是靜態(tài)的概念,gcc xx.x -o pro,磁盤中生成的pro就是程序。
進程是程序的一次運行活動,通俗的講就是程序跑起來了,系統(tǒng)中就多了一個進程。
查看系統(tǒng)中的進程
ps指令
查看系統(tǒng)中所有進程
ps -aux結(jié)果:
查看系統(tǒng)中的init進程
ps -aux | grep init結(jié)果:
即把ps -aux指令所有輸出的結(jié)果通過管道導(dǎo)向grep進行搜索,查找init關(guān)鍵字的文本
-aux 顯示所有包含其他使用者的進程
|管道符號
grep 用于查找文件里符合條件的字符串
top指令
類似windows的任務(wù)管理器,數(shù)據(jù)也是實時動態(tài)變化的。
進程標識符 使用getpid()獲取
每一個進程都有一個非負整數(shù)標識唯一的ID,即為pid。
調(diào)用getpid()獲取自身進程標識符,getppid()獲取獲取父進程標識符
其中系統(tǒng)所占用的進程標識符如下:
| pid = 0 | 交換進程 | 用于進程調(diào)度 | 所有“同時”在運行的程序所占用的資源受到進程調(diào)度的影響 |
| pid = 1 | init進程 | 用于系統(tǒng)初始化 | 程序運行,內(nèi)核加載完畢,文件系統(tǒng)起來的第一個進程就是init進程,讀取配置文件然后再啟動其他進程。(如ktv點歌機開機后看到的是點歌界面,而不是字符界面) |
父進程,子進程
進程A創(chuàng)建了進程B,A就是父進程,B就是子進程
2.創(chuàng)建進程fork
pid_t fork(void);
fork函數(shù)調(diào)用成功,返回兩次:返回值為0,代表當(dāng)前進程是子進程,非負數(shù)為父進程,如果調(diào)用失敗則返回-1
創(chuàng)建子進程的目的:復(fù)制父進程(此時兩個或兩個以上進程),父進程等待客戶端服務(wù)請求,當(dāng)這種請求到達時,父進程調(diào)用fork,讓子進程去處理(QQ服務(wù)器 客戶端 結(jié)合Socket網(wǎng)絡(luò)編程)
返回兩次:
應(yīng)用場景(模擬網(wǎng)絡(luò)等待):
#include <stdio.h> #include <sys/types.h> #include <unistd.h>int main() {pid_t pid;int data;while(1){printf("please input a data\n");scanf("%d",&data);if(data == 1){pid = fork();if(pid > 0){}else if(pid == 0){while(1){printf("do net request,pid=%d\n",getpid());sleep(3);}}}else{printf("wait, do nothing\n");}}return 0; }進程創(chuàng)建發(fā)生了什么——C程序的存儲空間如何分配
詳細參照博文:內(nèi)存四區(qū)(代碼區(qū) 靜態(tài)區(qū) 棧區(qū) 堆區(qū))
fork創(chuàng)建進程,所有的東西都進行了拷貝,包括全局變量、局部變量、代碼等,各進程內(nèi)改變變量的值互不影響。
詳細說明:
3.創(chuàng)建進程vfork(區(qū)別fork)
vfork與fork的區(qū)別:
1.(共用)vfork直接使用父進程的存儲空間,不拷貝(隨著內(nèi)核的發(fā)展,fork目前執(zhí)行的是寫時拷貝—copy on write 若子進程未改變原始變量的值時不會進行拷貝)
2.(等待)vfork保證子進程先運行,當(dāng)子進程調(diào)用exit退出后,父進程才執(zhí)行
運行結(jié)果:
4.進程退出
正常退出
1.main函數(shù)調(diào)用return
2.進程調(diào)用exit(),標準C庫
3.進程調(diào)用_exit()或者_Exit(),系統(tǒng)調(diào)用
4.進程最后一個線程返回
5.最后一個線程調(diào)用pthread_exit
異常退出
1.調(diào)用abort
2.當(dāng)進程收到某些信號時,如ctrl+c
3.最后一個線程對取消(cancellation)請求作出響應(yīng)
注意:不管進程如何終止,最后都會執(zhí)行內(nèi)核中的同一段代碼,這段代碼為相應(yīng)進程關(guān)閉所有打開描述符,釋放它所使用的存儲器等。
5.父進程等待子進程退出
父進程收集子進程退出狀態(tài)(僵尸進程)
父進程等待子進程退出并收集子進程退出狀態(tài)
子進程退出狀態(tài)不被收集,會變成僵尸進程
僵尸進程的例子:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h>int main() {pid_t pid;int i;int cnt=0;pid=fork();if(pid > 0){//父進程while(1){printf("這是父進程,pid=%d\n",getpid());printf("cnt=%d\n",cnt);sleep(2);//防止刷屏} }else if(pid == 0){//子進程for(i=0;i<5;i++){//看看是不是保證子進程先運行,五次過后推出進入父進程printf("這是子進程,pid=%d,這是第%d次\n",getpid(),i+1);cnt++;sleep(1);//防止刷屏}exit(-1);}return 0; }此時運行的結(jié)果是子進程退出了,但是退出狀態(tài)沒有被收集,子進程成了僵尸進程(zomb)。
等待退出函數(shù)wait()
wait(int *status): status參數(shù),他是一個整型數(shù)指針。 非空:子進程退出狀態(tài)放在它所指向的地址 空(NULL):不關(guān)心退出狀態(tài) #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h>int main() {pid_t pid;int i;int cnt=0;int status;pid=fork();if(pid > 0){//父進程wait(&status);printf("子進程退出,status:%d\n",WEXITSTATUS(status));//WEXITSTATUS()是解析子進程退出碼的宏,下面有詳細介紹while(1){printf("這是父進程,pid=%d\n",getpid());printf("cnt=%d\n",cnt);sleep(2);//防止刷屏} }else if(pid == 0){//子進程for(i=0;i<5;i++){//看看是不是保證子進程先運行,五次過后推出進入父進程printf("這是子進程,pid=%d,這是第%d次\n",getpid(),i+1);cnt++;sleep(1);//防止刷屏}exit(3);//退出碼返回給父進程中的wait進行收集}return 0; }解析出wait()對子進程退出碼回收的宏
等待退出函數(shù)waitpid()
來個例子:
結(jié)果如下所示,發(fā)現(xiàn)子進程也變?yōu)榻┦M程。
父進程先于子進程退出(孤兒進程)
父進程如果不等待子進程退出 ,在子進程之前就結(jié)束了自己的生命,此時的子進程就叫做是孤兒進程
Linux避免系統(tǒng)存在太多的孤兒進程,init進程收留孤兒進程,變成孤兒進程的父進程
例子:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h>int main() {pid_t pid;int i;int status;int cunt=0;pid=fork();if(pid > 0){printf("這是父進程,pid=%d\n",getpid());printf("cunt=%d\n",cunt);sleep(2);}else if(pid == 0){for(i=0;i<5;i++){printf("這是子進程,pid=%d,我的父進程的pid=%d\n",getpid(),getppid());cunt++;sleep(2);}exit(3);}return 0; }運行結(jié)果:
最后再來一個綜合的例子:
6.exec族函數(shù)(讓子進程調(diào)用其他程序)
參照博文:https://blog.csdn.net/u014530704/article/details/73848573
execl,execlp
#include <unistd.h> #include <stdio.h> #include <stdlib.h>int main(){//int execl(const char *path, const char *arg, .../* (char *) NULL */); /* path:可執(zhí)行文件的路徑名字arg:可執(zhí)行程序所帶的參數(shù),第一個參數(shù)為可執(zhí)行文件名字,沒有帶路徑且arg必須以NULL結(jié)束file:如果參數(shù)file中包含/,則就將其視為路徑名,否則就按 PATH環(huán)境變量,在它所指定的各目錄中搜尋可執(zhí)行文件exec族函數(shù)參數(shù)極難記憶和分辨,函數(shù)名中的字符會給我們一些幫助:l : 使用參數(shù)列表p:使用文件名,并從PATH環(huán)境進行尋找可執(zhí)行文件v:應(yīng)先構(gòu)造一個指向各參數(shù)的指針數(shù)組,然后將該數(shù)組的地址作為這些函數(shù)的參數(shù)。e:多了envp[]數(shù)組,使用新的環(huán)境變量代替調(diào)用進程的環(huán)境變量exec函數(shù)族的函數(shù)執(zhí)行成功后不會返回,調(diào)用失敗時,會設(shè)置errno并返回-1,然后從原程序的調(diào)用點接著往下執(zhí)行。 */printf("before execl\n");if(execl("/bin/ls","ls",NULL,NULL) == -1)//通過whereis指令找到ls命令的位置{printf("execl failed!\n");perror("why");}printf("after execl\n");return 0; } int main(){//int execlp(const char *file, const char *arg, .../* (char *) NULL */);printf("before execl\n");if(execlp("ls","ls","-l",NULL) == -1)//不用找到命令的路徑{printf("execl failed!\n");perror("why");}printf("after execl\n");return 0; }運行結(jié)果:
通過whereis指令查找ls、date(獲取系統(tǒng)時間)系統(tǒng)指令的位置:
execv execvp
int main(){//int execv(const char *path, char *const argv[]); char* argv[]={"ls",NULL,NULL};if(execv("/bin/ls",argv) == -1){printf("execl failed!\n");perror("why");}printf("after execl\n");return 0; } int main(){// int execvp(const char *file, char *const argv[]);char* argv[]={"ls","-l",NULL};if(execvp("ls",argv) == -1){printf("execl failed!\n");perror("why");}printf("after execl\n");return 0; }exec配合fork使用
應(yīng)用場景:在執(zhí)行A程序的過程中讓子進程去執(zhí)行B程序
代碼B(用來修改配置文件) 編譯生成程序名為change
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <sys/wait.h>int main() {int fd;int n_write;int n_read;pid_t pid;char *readBuf;fd=open("test15.txt",O_RDWR);int size=lseek(fd,0,SEEK_END);//計算文件的大小lseek(fd,0,SEEK_SET);//光標重新到開頭readBuf=(char *)malloc(sizeof(char)*size+8 );//+8防止溢出n_read=read(fd,readBuf,size);char *p=strstr(readBuf,"length=");if(p == NULL ){printf("沒有找到\n");exit(-1);}p=p+strlen("length=");//指針往后移動大哦想要改的地方*p='9';//將里面的6改稱9lseek(fd,0,SEEK_SET);n_write=write(fd,readBuf,strlen(readBuf));close(fd);exit(0); }代碼A
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <sys/wait.h>int main() {int fd;int n_write;int n_read;pid_t pid;int data;char *readBuf;while(1){printf("請輸入一個數(shù)\n");scanf("%d",&data);if(data == 1){printf("成功進入\n");pid=fork();if(pid > 0){//父進程wait(NULL);//防止子進程變成僵尸進程}if(pid == 0){//子進程execl("./change","change",NULL,NULL);//獲取系統(tǒng)時間也能實現(xiàn)}}else{printf("等待輸入正確的指令\n");}}return 0; }初始的test15.txt
運行程序A后
system函數(shù)
本質(zhì)上是對execl函數(shù)的二次封裝,可查看其源碼。實際上比execl更好用
和execl區(qū)別:system執(zhí)行完該函數(shù)后,還會繼續(xù)執(zhí)行后面的函數(shù) ,而exec則不會。
popen函數(shù)
比system在應(yīng)用中的好處:可以獲取運行的輸出結(jié)果
#include <stdio.h> FILE *popen(const char *command, const char *type); int pclose(FILE *stream); #include <stdio.h> #include <stdlib.h>// FILE *popen(const char *command, const char *type); // FILE *fopen(const char *path, const char *mode); // size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); int main(){// system("ls");FILE* fp;char ret[1024];fp=popen("ls -l","r");int n_read=fread(ret,1,1024,fp);if(n_read != -1){printf("read sucess!\n");printf("return n_read=%d,read data=%s\n",nread,ret);}else{printf("no read datas\n");}return 0; }總結(jié)
以上是生活随笔為你收集整理的Linux进程编程(PS: exec族函数、system、popen函数)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022年最完整的html网页跳转代码大
- 下一篇: Linux进程间通信(管道、消息队列、共