Linux进程
来源:互联网 发布:中国造不出圆珠笔知乎 编辑:程序博客网 时间:2024/06/05 03:39
1. execve()函数
前面讲到的execX函数簇,它们都是标准库函数里的函数,也就是说它们是对系统调用API的封装,这个系统调用的API就是execve()函数,原型为:
int execve(const char *filename, char *const argv[], char *const envp[]);
调用方法还是类似之前:
// 主调进程int main(void){ char* argv[] = { "hello", "world", NULL }; char* envp[] = { "PATH = abcdef", "a = wwww", NULL }; execve("./new_pro", argv, envp); return 0;}
// 新启动的程序extern char** environ;int main(int argc, char** argv){ printf("new_pro: pid = %d, ppid = %d\n", getpid(), getppid()); // 打印主调进程传来的参数 while (*argv != NULL) printf("%s ", *(argv++)); printf("\n"); // 打印主调进程传来的环境变量 while (*environ != NULL) printf("%s ", *(environ++)); printf("\n"); return 0;}
运行结果:
2. fork()函数
为了让新程序不覆盖主调进程的进程空间,引入了fork()系统调用,即创建子进程。
int main(void){ pid_t pid; pid = fork(); printf("pid = %d\n", pid); return 0;}
运行结果:
代码”printf(“pid = %d\n”, pid);”被执行了两次,分别是子进程和父进程(即当前进程)执行的。
在32位的操作系统中,每个进程的进程片空间是4G,其中0-1G是内核空间,2-3G是用户空间。当然,这个空间指的是虚拟内存。
fork()函数里面的实现体可以划分为两部分: 创建进程和复制把父进程的进程空间赋值给子进程,创建完子进程后,主调进程的进程片4G空间被复制给子进程4G空间。执行流程可以概图如下:
在父进程中返回的是子进程的pid,所以是返回值大于0。在子进程返回0,0在这里只是作为子进程的标志。
2.1 子进程的进程空间由父进程的内存空间复制而得
子进程的进程空间是由父进程的内存空间复制而得,那么在父进程fork()之前的文件描述符、变量也会复制给子进程了。
int main(void){ pid_t pid; char buf[10] = {}; printf("main, pid = %d\n", getpid()); int a = 10; int fd; fd = open("./test.file", O_CREAT | O_RDWR, 0666); if (fd < 0) { perror("open"); return -1; } if ((pid = fork()) < 0) { perror("fork"); return -1; } if (pid == 0) { printf("child, pid = %d\n", getpid()); printf("child, &a = %p\n", &a); a += 2; //子进程中修改父进程复制来的a变量 if (read(fd, buf, sizeof(buf)) < 0) { perror("read"); exit(1); } printf("child read success!!\n"); exit(1); } printf("parent, &a = %p\n", &a); printf("parent, a = %d\n", a); close(fd); return 0;}
运行结果:
子进程仍能访问父进程的文件描述符;在子进程中修改变量a,因为它修改的是父进程a的副本,所以并不会影响父进程的变量a。注意,在子父进程中,即使a的地址是一样的,但是这仅仅是虚拟空间的地址一样。子父进程拥有各自的4G虚拟进程空间,它们被映射到内存不同的两个块物理地址,物理地址不同,所以a变量互不干涉。
2.2 子进程独立虚拟内存空间
如上所说的,子父进程是完全不干涉的两个进程空间,所以它们之间的不能直接通讯。但是注意,两个进程的头1G是内核空间,内核空间是共同的,所以要让二者通讯,就需要借助内核的API,说白了就是系统调用。
2.3 子进程创建成功后,父子进程同时运行
从上面几个程序的结果可知,子进程创建成功后,父子进程是同时运行的。由于父进程直接回往代码下执行,所以先退出了,接着子进程运行完退出。有些时候,父进程要等到子进程退出后才能退出,这就需要wait()或者waitpid()系统API,其原型如下:
pid_t wait(int *status); //阻塞等待子进程退出,只要子有一个子进程退出,该函数得到返回 pid_t waitpid(pid_t pid, int *status, int options); //指定等待哪一个进程,options可以取消阻塞等待
返回值是pid, 也就是返回退出的子进程的pid;参数status用于保存进程的退出状态,若不想获取该状态,可以设为NULL。若父进程想知道子进程的退出状态,可利用如下几个函数可以解析status:
WEXITSTATUS(status) //程序运行中,有时候我们会返回自定义的错误标号,这个宏就是来捕捉exit的错误编号(0-255)WIFEXITED(status) //若自己退出,返回为1否则返回0WIFSIGNALED(status) //若被其他进程kill掉,返回为1否则返回0...
3. execve()函数的参数
程序在操作系统中运行,就会形成一个进程。每个进程都有自己的pid。通过命令”ps -aux”可以看到当前系统中正在运行的进程以及进程状态。常见进程状态如下:
D 无法中断的休眠状态(通常IO的进程)R 正在运行中T 停止或被追踪S 处于休眠状态Z 僵尸进程< 优先级高的进程N 优先级较低的进程
看下面代码:
// 主调进程int main(void){ int pid; char* argv[] = { "hello", "world", NULL }; char* envp[] = { "PATH = abcdef", "a = wwww", NULL }; printf("main: pid = %d, ppid = %d\n", getpid(), getppid()); pid = fork(); // 创建子进程来执行新程序 if (pid == 0) { // 执行新程序 execve("./new_pro", argv, envp); } else getchar(); return 0;}
// 新程序extern char** environ;int main(int argc, char** argv){ printf("new_pro: pid = %d, ppid = %d\n", getpid(), getppid()); while (*argv != NULL) printf("%s ", *(argv++)); printf("\n"); while (*environ != NULL) printf("%s ", *(environ++)); printf("\n"); printf("new_pro: "); getchar(); return 0;}
运行结果:
此时运行”pa -aux”得到:
可以看到a.out进程却看不到new_pro进程。这个是跟execve()函数的形参有关。argv[0]默认是进程的名称,所以要想在在进程查看命令中看到新程序进程,需要传入新程序进程名称,argv的定义为:
char* argv[] = { "/new_pro", "hello", "world", NULL };
再次运行:
3. 孤儿进程
父进程创建子进程,子进程在运行中,父进程先结束,子进程就会变成孤儿进程。
int main(void){ printf("main, pid = %d\n", getpid()); if (fork() == 0) { // 子进程 printf("child, pid = %d, ppid = %d\n", getpid(), getppid()); sleep(10); // 子进程在休眠的10s间,父进程已经退出 printf("\nchild, pid = %d, ppid = %d\n", getpid(), getppid()); exit(0); } sleep(2); printf("main is exit!\n"); return 0;}
运行结果 :
子进程尚在运行,若父进程退出了,那么其ppid将更改为1,也就是init进程。这个进程是Linux的第一个启动的进程,所有的进程都是由它而来的。
4. 僵尸进程
子进程结束后,父进程并未结束且没有对子进程做任何操作,子进程就为僵尸进程状态存在。子进程等待父进程去取子进程的运行结果或者执行状态。
int main(void){ printf("main, pid = %d\n", getpid()); if (fork() == 0) { printf("\nchild, pid = %d, ppid = %d\n", getpid(), getppid()); exit(0); } getchar(); printf("main is exit!\n"); return 0;}
运行结果:
此时在另一窗口执行命令”pa -aux”:
pid = 10697的子进程就变为僵尸进程了。当父进程退出后,该僵尸进程才会消失。僵尸进程的解决办法是父进程调用wait()函数等待子进程:
int main(void){ printf("main, pid = %d\n", getpid()); if (fork() == 0) { printf("\nchild, pid = %d, ppid = %d\n", getpid(), getppid()); exit(0); } wait(NULL); getchar(); printf("main is exit!\n"); return 0;}
- Linux--进程--僵尸进程
- linux 进程
- linux 进程
- Linux 进程
- Linux进程
- LINUX进程
- Linux进程
- Linux进程
- Linux进程
- linux进程
- Linux进程
- 进程-Linux
- linux-进程
- Linux进程
- linux 进程
- linux进程
- linux进程
- Linux进程
- 2017CCCC决赛 L1-3. 阅览室
- Android仿华为天气绘制刻度盘
- -Java连接Excel、MySQL、SQLServer数据源及相关增、删、改、查方法和问题总结-【Part II】
- C++抽象编程——递归策略(3)——foreach语句的简单实现
- RTX:RTX实时操作系统(RTOS)简介学习笔记
- Linux进程
- java基础总结20-java常用API(基本类型包装类)
- C语言综合实践7.18
- Get started with Docker 中文文档(3)—— Part 3: Services
- sed工具
- 深入浅出控制反转/依赖注入(IoC/DI)(转)
- 北大28楼已拆
- 诡异的电梯【Ⅰ】
- StringBuilder与StringBuffer的区别(转)