Linux之进程编程
一、进程关键概念
- 什么是程序,什么是进程,有什么区别?
- 程序是静态的概念,gcc xxx.c –o pro
- 磁盘中生成pro文件,叫做程序
- 进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程
- 如何查看系统中有哪些进程?
- 使用ps指令查看。 实际工作中,配合grep来查找程序中是否存在某一个进程。
例如:ps -aux|grep init - 使用top指令查看,类似windows任务管理器
- 使用ps指令查看。 实际工作中,配合grep来查找程序中是否存在某一个进程。
- 什么是进程标识符?
- 每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
- Pid=0: 称为交换进程(swapper),作用—进程调度。
Pid=1:init进程,作用—系统初始化。 - 编程调用getpid函数获取自身的进程标识符
getppid获取父进程的进程标识符
- 什么叫父进程,什么叫子进程
- 进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
- C程序的存储空间是如何分配?
二、使用fork函数创建一个进程
- 参数说明
pid_t fork(void); //fork函数调用成功,返回两次 //返回值为0, 代表当前进程是子进程 //返回值非负数,代表当前进程为父进程 //调用失败,返回-1
- fork创建一个子进程的一般目的
- 源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if(pid == 0) { while(1) { printf("this is child process, pid = %d\n", getpid()); sleep(2); } } else { while(1) { printf("this is father process, pid = %d\n", getpid()); sleep(2); } } return 0; }
三、vfork函数也可以创建进程,与fork有什么区别
- vfork 直接使用父进程存储空间,不拷贝。
- vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
- 源码:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; int cnt = 0; pid = vfork(); if(pid == 0) { while(1) { printf("this is child process, pid = %d\n", getpid()); sleep(1); cnt++; if(cnt == 3) { exit(0); } } } else { while(1) { printf("this is father process, pid = %d\n", getpid()); sleep(1); printf("cnt = %d\n",cnt); } } return 0; }
四、exit进程退出
- 正常退出
- Main函数调用return
- 进程调用exit(),标准c库
- 进程调用_exit()或者_Exit(),属于系统调用
- 进程最后一个线程返回
- 最后一个线程调用pthread_exit
- 异常退出
- 调用abort
- 当进程收到某些信号时,如ctrl+C
- 最后一个线程对取消(cancellation)请求做出响应
- 参数说明
- 源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; int cnt = 0; pid = fork(); if(pid == 0) { while(1) { printf("this is child process, pid = %d\n", getpid()); sleep(1); cnt++; if(cnt == 3) { exit(0); } } } else { while(1) { printf("this is father process, pid = %d\n", getpid()); sleep(1); printf("cnt = %d\n",cnt); } } return 0; }
五、wait、waitpid等待子进程
- 为啥要等待子进程退出
子进程退出状态不被收集,变成僵死进程(僵尸进程)。 - 父进程等待子进程退出,并收集子进程的退出状态,通过WEXITSTATUS()函数进行解析。
- 相关函数参数说明
区别:waip使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
status参数:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态
- wait函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; int cnt = 0; int status = 10; pid = fork(); if(pid == 0) { while(1) { printf("this is child process, pid = %d\n", getpid()); sleep(1); cnt++; if(cnt == 3) { exit(3); } } } else { wait(&status); printf("child process status's = %d\n", WEXITSTATUS(status)); while(1) { printf("this is father process, pid = %d\n", getpid()); sleep(1); printf("cnt = %d\n",cnt); } } return 0; }
- waitpid函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; int cnt = 0; int status = 10; pid = fork(); if(pid == 0) { while(1) { printf("this is child process, pid = %d\n", getpid()); sleep(1); cnt++; if(cnt == 3) { exit(3); } } } else { waitpid(pid, &status, WNOHANG); printf("child process status's = %d\n", WEXITSTATUS(status)); while(1) { printf("this is father process, pid = %d\n", getpid()); sleep(1); printf("cnt = %d\n",cnt); } } return 0; }
六、孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
七、exec族函数
-
参考博文:https://blog.csdn.net/u014530704/article/details/73848573
-
exec族函数函数的作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。 -
功能:
在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 -
函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe -
函数原型:
#include <unistd.h> extern char **environ; 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[]); int execvpe(const char *file, char *const argv[],char *const envp[]);
-
返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。 -
参数说明:
path: 可执行文件的路径名字
arg: 可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file: 如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。 -
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p: 使用文件名,并从PATH环境进行寻找可执行文件
v: 应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e: 多了envp[]数组,使用新的环境变量代替调用进程的环境变量 -
为什么要用exec族函数,有什么作用
-
execl函数源码
mainecho.c#include <stdio.h> int main(int argc, char *argv[]) { int i; for(i=0; i<argc; i++) { printf("argv[%d] = %s\n", i, argv[i]); } }
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("before execl\n"); if(execl("./bin/mainecho", "echoarg", "abc", NULL) == -1) { printf("excel failed\n"); perror("why\n"); } printf("after execl\n"); return 0; }
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("before execl\n"); if(execl("/bin/ls", "-l", NULL) == -1) { printf("excel failed\n"); perror("why\n"); } printf("after execl\n"); return 0; }
-
execlp函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("before execl\n"); if(execlp("ls", "-l", NULL) == -1) { printf("excel failed\n"); perror("why\n"); } printf("after execl\n"); return 0; }
-
execv函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { char *argv[] = {"/bin/ls","-l", NULL}; printf("before execl\n"); if(execv("/bin/ls", argv) == -1) { printf("excel failed\n"); perror("why\n"); } printf("after execl\n"); return 0; }
-
execvp函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { char *argv[] = {"ls","-l", NULL}; printf("before execl\n"); if(execvp("ls", argv) == -1) { printf("excel failed\n"); perror("why\n"); } printf("after execl\n"); return 0; }
八、exec配合fork使用
- 实现功能: 当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。
- 源码: Charge_Config.c
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { int fdSrc; char *Readbuf=NULL; if(argc != 2) { printf("param error\n"); exit(-1); } fdSrc = open(argv[1], O_RDWR); int size = lseek(fdSrc, 0, SEEK_END); lseek(fdSrc, 0, SEEK_SET); Readbuf = (char *)malloc((sizeof(char)) * size + 8); read(fdSrc, Readbuf, size); char *str = strstr(Readbuf, "LENG="); str = str+strlen("LENG="); *str = '5'; lseek(fdSrc, 0, SEEK_SET); write(fdSrc, Readbuf, size); close(fdSrc); free(Readbuf); return 0; }
- main.c
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { pid_t pid; int data; while(1) { printf("pleas input a data\n"); scanf("%d", &data); if(data == 1) { pid = fork(); if(pid == 0) { while(1) { printf("this is child process pid = %d\n", getpid()); execl("./Change_Config", "Change_Config", "Config", NULL); } } else { wait(NULL); } } else { printf("father process donothing\n"); } } return 0; }
九、system函数
- 函数说明
system()函数的返回值如下:成功,则返回进程的状态值;当sh不能执行时,返回127;失败返回-1。 - 函数源码
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; int data; while(1) { printf("pleas input a data\n"); scanf("%d", &data); if(data == 1) { pid = fork(); if(pid == 0) { while(1) { printf("this is child process pid = %d\n", getpid()); //execl("./Change_Config", "Change_Config", "Config", NULL); system("./Change_Config Config"); exit(0); } } else { wait(NULL); } } else { printf("father process donothing\n"); } } return 0; }
十、popen函数
- 函数说明
比system在应用中的好处: 可以获取运行的输出结果。 - 函数源码
#include <stdio.h> //FILE *popen(const char *command, const char *type); //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); int main(void) { FILE *pd; char buf[1024] = {0}; pd = popen("ps", "r"); int nread = fread(buf, 1024, 1, pd); printf("nread = %d, buf = \n", nread); printf("%s\n", buf); pclose(pd); return 0; }