Linux进程编程(PS: exec族函数、system、popen函数)
目錄
- 1.進程相關概念
- 程序和進程
- 查看系統中的進程
- ps指令
- top指令
- 進程標識符 使用getpid()獲取
- 父進程,子進程
- 2.創建進程fork
- 進程創建發生了什么——C程序的存儲空間如何分配
- 3.創建進程vfork(區別fork)
- 4.進程退出
- 正常退出
- 異常退出
- 5.父進程等待子進程退出
- 父進程收集子進程退出狀態(僵尸進程)
- 等待退出函數wait()
- 等待退出函數waitpid()
- 父進程先于子進程退出(孤兒進程)
- 6.exec族函數(讓子進程調用其他程序)
- execl,execlp
- execv execvp
- exec配合fork使用
- system函數
- popen函數
1.進程相關概念
程序和進程
程序是靜態的概念,gcc xx.x -o pro,磁盤中生成的pro就是程序。
進程是程序的一次運行活動,通俗的講就是程序跑起來了,系統中就多了一個進程。
查看系統中的進程
ps指令
查看系統中所有進程
ps -aux結果:
查看系統中的init進程
ps -aux | grep init結果:
即把ps -aux指令所有輸出的結果通過管道導向grep進行搜索,查找init關鍵字的文本
-aux 顯示所有包含其他使用者的進程
|管道符號
grep 用于查找文件里符合條件的字符串
top指令
類似windows的任務管理器,數據也是實時動態變化的。
進程標識符 使用getpid()獲取
每一個進程都有一個非負整數標識唯一的ID,即為pid。
調用getpid()獲取自身進程標識符,getppid()獲取獲取父進程標識符
其中系統所占用的進程標識符如下:
| pid = 0 | 交換進程 | 用于進程調度 | 所有“同時”在運行的程序所占用的資源受到進程調度的影響 |
| pid = 1 | init進程 | 用于系統初始化 | 程序運行,內核加載完畢,文件系統起來的第一個進程就是init進程,讀取配置文件然后再啟動其他進程。(如ktv點歌機開機后看到的是點歌界面,而不是字符界面) |
父進程,子進程
進程A創建了進程B,A就是父進程,B就是子進程
2.創建進程fork
pid_t fork(void);
fork函數調用成功,返回兩次:返回值為0,代表當前進程是子進程,非負數為父進程,如果調用失敗則返回-1
創建子進程的目的:復制父進程(此時兩個或兩個以上進程),父進程等待客戶端服務請求,當這種請求到達時,父進程調用fork,讓子進程去處理(QQ服務器 客戶端 結合Socket網絡編程)
返回兩次:
應用場景(模擬網絡等待):
#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; }進程創建發生了什么——C程序的存儲空間如何分配
詳細參照博文:內存四區(代碼區 靜態區 棧區 堆區)
fork創建進程,所有的東西都進行了拷貝,包括全局變量、局部變量、代碼等,各進程內改變變量的值互不影響。
詳細說明:
3.創建進程vfork(區別fork)
vfork與fork的區別:
1.(共用)vfork直接使用父進程的存儲空間,不拷貝(隨著內核的發展,fork目前執行的是寫時拷貝—copy on write 若子進程未改變原始變量的值時不會進行拷貝)
2.(等待)vfork保證子進程先運行,當子進程調用exit退出后,父進程才執行
運行結果:
4.進程退出
正常退出
1.main函數調用return
2.進程調用exit(),標準C庫
3.進程調用_exit()或者_Exit(),系統調用
4.進程最后一個線程返回
5.最后一個線程調用pthread_exit
異常退出
1.調用abort
2.當進程收到某些信號時,如ctrl+c
3.最后一個線程對取消(cancellation)請求作出響應
注意:不管進程如何終止,最后都會執行內核中的同一段代碼,這段代碼為相應進程關閉所有打開描述符,釋放它所使用的存儲器等。
5.父進程等待子進程退出
父進程收集子進程退出狀態(僵尸進程)
父進程等待子進程退出并收集子進程退出狀態
子進程退出狀態不被收集,會變成僵尸進程
僵尸進程的例子:
#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; }此時運行的結果是子進程退出了,但是退出狀態沒有被收集,子進程成了僵尸進程(zomb)。
等待退出函數wait()
wait(int *status): status參數,他是一個整型數指針。 非空:子進程退出狀態放在它所指向的地址 空(NULL):不關心退出狀態 #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()對子進程退出碼回收的宏
等待退出函數waitpid()
來個例子:
結果如下所示,發現子進程也變為僵尸進程。
父進程先于子進程退出(孤兒進程)
父進程如果不等待子進程退出 ,在子進程之前就結束了自己的生命,此時的子進程就叫做是孤兒進程
Linux避免系統存在太多的孤兒進程,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; }運行結果:
最后再來一個綜合的例子:
6.exec族函數(讓子進程調用其他程序)
參照博文: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:可執行文件的路徑名字arg:可執行程序所帶的參數,第一個參數為可執行文件名字,沒有帶路徑且arg必須以NULL結束file:如果參數file中包含/,則就將其視為路徑名,否則就按 PATH環境變量,在它所指定的各目錄中搜尋可執行文件exec族函數參數極難記憶和分辨,函數名中的字符會給我們一些幫助:l : 使用參數列表p:使用文件名,并從PATH環境進行尋找可執行文件v:應先構造一個指向各參數的指針數組,然后將該數組的地址作為這些函數的參數。e:多了envp[]數組,使用新的環境變量代替調用進程的環境變量exec函數族的函數執行成功后不會返回,調用失敗時,會設置errno并返回-1,然后從原程序的調用點接著往下執行。 */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; }運行結果:
通過whereis指令查找ls、date(獲取系統時間)系統指令的位置:
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使用
應用場景:在執行A程序的過程中讓子進程去執行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("請輸入一個數\n");scanf("%d",&data);if(data == 1){printf("成功進入\n");pid=fork();if(pid > 0){//父進程wait(NULL);//防止子進程變成僵尸進程}if(pid == 0){//子進程execl("./change","change",NULL,NULL);//獲取系統時間也能實現}}else{printf("等待輸入正確的指令\n");}}return 0; }初始的test15.txt
運行程序A后
system函數
本質上是對execl函數的二次封裝,可查看其源碼。實際上比execl更好用
和execl區別:system執行完該函數后,還會繼續執行后面的函數 ,而exec則不會。
popen函數
比system在應用中的好處:可以獲取運行的輸出結果
#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; }總結
以上是生活随笔為你收集整理的Linux进程编程(PS: exec族函数、system、popen函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022年最完整的html网页跳转代码大
- 下一篇: Linux进程间通信(管道、消息队列、共