进程的基本控制
2017-12-10 23:19
281 查看
前言:整个操作系统都在围绕进程这一概念具体展开,所以对于进程的控制就显得十分重要,这篇文章主要讲述以下几点:
1. 进程创建
2. 进程退出
3. 进程等待
4. 进程程序替换
为什么要基于这样的父子进程关系呢?试想一下,我们和操作系统打交道是通过shell内建的bash,如果用户的什么请求都经由bash去亲自执行,那么一个bash也不够用啊,其次,如果一旦请求中出了问题,那么bash挂掉的话,谁来帮我们向操作系统传达我们的请求呢?
基于上面提出的种种问题,就引出进程创建子进程的必要性了。
进程的创建方式:
pid_t fork(void);
pid_t vfork(void);
进程调用fork,当控制权限转移到内核中的fork代码后:
分配新的内存块和数据结构给子进程
将父进程的大多数数据结构拷贝至子进程
添加子进程到系统进程列表
fork返回,操作系统进行进程调度
当一个进程fork出一个子进程后,就有两个二进制代码相同的进程,并且运行到相同的地方,但每个进程都将开始执行自己的代码。
如下:
这里需要注意的是,先执行子进程还是父进程完全取决于操作系统的进程调度器决定。
fork失败的原因:
系统中的进程数达到了上限
系统的内存不足
系统不支持,如Windows不支持fork
子进程一定先于父进程执行。
子进程调用exec或者exit之后父进程才能执行
代码运行完,结果正确
代码运行完,结果不正确
代码异常终止
常见进程退出:
正常终止:
1. main函数返回
2. 调用exit函数
3. 调用_exit函数
exit函数和_exit函数的区别:
exit会进行清理工作,如刷新缓冲区等,而exit直接退出程序
_exit是系统调用,exit最终也会调用_exit。
异常终止
CTRL+C/kill -9
具体的程序退出部分,可见博客尾部的链接。
并且作为父进程,创建子进程是让它完成一些任务的,总要知道它返回的结果,完成的怎么样。
status
所以查看的话,先查看低七位是否为0 ,如果是0代表程序正常退出,可以查看高八位具体的退出码。
如果低七为不为0,则代表信号终止,高八位就没有意义了,可以查看低七位的具体信号。
core dump是指进程终止时所记录的现场。
以下面的代码为实例:
当子进程正常退出时,会返回退出码。
运行结果:
接下来,我们直接kill -9掉该进程,结果应该返回9号信号,看如下运行结果:
我的Ubuntu是最新的,本应该返回9,但是操作系统将9这个数字,对应成第九个信号的名称显示出来。
下面是Linux下的信号:
返回值:
正常返回收集子进程的进程ID
如果设置了选项WNOHANG,而调用waitpid发现没有已退出的子进程可以回收,则返回0,就是轮询等待的意思
如果调用中出错,返回-1,errno会被设置成相应的值以指示错误所在。
参数:
pid:
pid = -1,等待任意一个子进程,与wait等效
pid > 0,等待其进程ID与pid相等的进程
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真(相当于上面的检查低7位为是否为0)
WEXITSTATUS(status):若WIFIXITED非零,提取子进程退出码(相当于上面的查看高8位的退出码)
option:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回该进程的ID
需要注意的是:
如果子进程已经结束,调用wait/waitpid时,函数会直接返回,并且释放资源,获得子进程退出信息。(对应的场景就是子进程退出时,父进程在沉睡,如果这时父进程不予以处理子进程则会产生僵尸进程)
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞
如果不存在该子进程,则立即出错返回
如下代码示例:
v(vector):表示参数采用数组
p(path):带p的函数会自动搜索环境变量PATH
e(env):表示自己维护环境变量
需要特别注意的时,
如果exec函数族调用成功,则从新程序的启动代码开始,所以没有返回值
如果调用失败,则返回-1
代码示例:
虽然exec函数族有六个函数,但是只有execve函数是系统调用,其他几个函数最终都会调用execve函数。
基于程序替换,可以实现一个简单的shell。
exit函数详解http://blog.csdn.net/qq_36528114/article/details/71321390
实现一个简单的shell:http://blog.csdn.net/qq_36528114/article/details/72582588
1. 进程创建
2. 进程退出
3. 进程等待
4. 进程程序替换
进程创建
在操作系统中,对于父子进程的概念非常重要,必要linux自带的bash,对于你在命令行输入的一些指令,它是不会自己去处理你这些请求的,而是通过创建子进程去处理,它只需要知道子进程返回的消息就好了。为什么要基于这样的父子进程关系呢?试想一下,我们和操作系统打交道是通过shell内建的bash,如果用户的什么请求都经由bash去亲自执行,那么一个bash也不够用啊,其次,如果一旦请求中出了问题,那么bash挂掉的话,谁来帮我们向操作系统传达我们的请求呢?
基于上面提出的种种问题,就引出进程创建子进程的必要性了。
进程的创建方式:
pid_t fork(void);
pid_t vfork(void);
认识fork函数
pid_t fork(void) 返回值:pid_t其实就是一个整型,typedef成pid_t只是为了一眼看上去知道这是一个进程号 子进程中返回值为0. 父进程中返回操作系统给子进程分配的pid号。 fork失败返回-1
进程调用fork,当控制权限转移到内核中的fork代码后:
分配新的内存块和数据结构给子进程
将父进程的大多数数据结构拷贝至子进程
添加子进程到系统进程列表
fork返回,操作系统进行进程调度
当一个进程fork出一个子进程后,就有两个二进制代码相同的进程,并且运行到相同的地方,但每个进程都将开始执行自己的代码。
如下:
int main() { printf("Before fork: pid is:%d\n",getpid())//getpid函数为获取进程pid pid_t id = fork(); if(id < 0) { perror("fork error"); exit(1); } printf("After fork:pid is %d\n",getpid()); return 0; }
这里需要注意的是,先执行子进程还是父进程完全取决于操作系统的进程调度器决定。
fork失败的原因:
系统中的进程数达到了上限
系统的内存不足
系统不支持,如Windows不支持fork
认识vfork函数
对于vfork来说,其他的都是fork函数用法一样,只要记住最重要的两个特性就好。子进程一定先于父进程执行。
子进程调用exec或者exit之后父进程才能执行
进程终止
进程退出的场景:代码运行完,结果正确
代码运行完,结果不正确
代码异常终止
常见进程退出:
正常终止:
1. main函数返回
2. 调用exit函数
3. 调用_exit函数
exit函数和_exit函数的区别:
exit会进行清理工作,如刷新缓冲区等,而exit直接退出程序
_exit是系统调用,exit最终也会调用_exit。
异常终止
CTRL+C/kill -9
具体的程序退出部分,可见博客尾部的链接。
进程等待
进程等待是非常重要的,如果父进程对子进程不管不顾的话,那么可能会产生僵尸进程,从而造成内存泄漏。并且作为父进程,创建子进程是让它完成一些任务的,总要知道它返回的结果,完成的怎么样。
wait函数
pid_t wait(int *status)//阻塞式等待 返回值:成功返回等待进程的pid,失败返回-1 参数:输出型参数,获取子进程的退出状态,不关心可以为NULL,该参数由操作系统初始化
status
所以查看的话,先查看低七位是否为0 ,如果是0代表程序正常退出,可以查看高八位具体的退出码。
如果低七为不为0,则代表信号终止,高八位就没有意义了,可以查看低七位的具体信号。
core dump是指进程终止时所记录的现场。
以下面的代码为实例:
#include<stdio.h> #include<wait.h> int main() { pid_t id = fork(); if(id > 0) { //father int status = 0; int ret = wait(&status); if(ret > 0 && (status&0x7f) == 0) { //success printf("child exit code:%d\n",(status>>8)); } else { //signal exit printf("signal code:%d\n",(status>>8)&0xff); } } else if(id == 0) { //child sleep(3); exit(5);//子进程的退出码 } else { perror("fork"); } return 0; }
当子进程正常退出时,会返回退出码。
运行结果:
接下来,我们直接kill -9掉该进程,结果应该返回9号信号,看如下运行结果:
我的Ubuntu是最新的,本应该返回9,但是操作系统将9这个数字,对应成第九个信号的名称显示出来。
下面是Linux下的信号:
waitpid函数
pid_t waitpid(pid_t pid,int *status,int option)//如果最后一个参数设置了WNOHANG就是非阻塞式等待
返回值:
正常返回收集子进程的进程ID
如果设置了选项WNOHANG,而调用waitpid发现没有已退出的子进程可以回收,则返回0,就是轮询等待的意思
如果调用中出错,返回-1,errno会被设置成相应的值以指示错误所在。
参数:
pid:
pid = -1,等待任意一个子进程,与wait等效
pid > 0,等待其进程ID与pid相等的进程
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真(相当于上面的检查低7位为是否为0)
WEXITSTATUS(status):若WIFIXITED非零,提取子进程退出码(相当于上面的查看高8位的退出码)
option:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回该进程的ID
需要注意的是:
如果子进程已经结束,调用wait/waitpid时,函数会直接返回,并且释放资源,获得子进程退出信息。(对应的场景就是子进程退出时,父进程在沉睡,如果这时父进程不予以处理子进程则会产生僵尸进程)
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞
如果不存在该子进程,则立即出错返回
如下代码示例:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id > 0) { //father int status = 0; pid_t ret = 0; do { ret = waitpid(-1,&status,WNOHANG);//no-blocking if(ret == 0) { printf("child is running\n"); } sleep(1); } while(ret == 0); if(WIFEXITED(status) && ret == id) { printf("wait child 3s success,child return code is:%d\n",WEXITSTATUS(status)); } else { printf("wait child failed,return\n"); return 1; } } else if(id == 0) { //child printf("child is run ,pid is:%d\n",getpid()); sleep(3); exit(1); } else { printf("%s fork error\n",__FUNCTION__); return 1; } return 0; }
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程一般要调用一种exec函数以执行另一个程序。当进程调用exec函数族时,该进程的用户空间代码和数据完全被新的程序替换,从新程序的启动例程开始执行,调用exec并不创建新进程,所以调用exec前后该进程的pid并未改变。exec函数族
#include<unistd.h> 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 execve(const char *path,char *const argv[],char *const envp[]);
函数解释
l(list):表示参数采用列表v(vector):表示参数采用数组
p(path):带p的函数会自动搜索环境变量PATH
e(env):表示自己维护环境变量
需要特别注意的时,
如果exec函数族调用成功,则从新程序的启动代码开始,所以没有返回值
如果调用失败,则返回-1
代码示例:
#include<stdio.h> #include<unistd.h> int main() { const *const argv[] = {"ls","-al",NULL}; char *const envp[] = {"PATH=/bin:/usr/bin",NULL};//环境变量 execl("/bin/ls","ls","-al".NULL); //带p的函数,不必再给出全路径 execlp("ls","ls","-al",NULL); //带e的,需要自己配置环境变量 execle("ls","ls","-al".NULL,envp); execv("/bin/ls",argv); //带p的,不需要给出全路径 execvp("ls",argv); //带e的,需要自己配置环境变量 execve("/bin/ls",argv,envp); exit(0); }
虽然exec函数族有六个函数,但是只有execve函数是系统调用,其他几个函数最终都会调用execve函数。
基于程序替换,可以实现一个简单的shell。
exit函数详解http://blog.csdn.net/qq_36528114/article/details/71321390
实现一个简单的shell:http://blog.csdn.net/qq_36528114/article/details/72582588
相关文章推荐
- 笔记10: 进程控制的基本概念
- 进程的基本属性:进程ID、父进程ID、进程组ID、会话和控制终端
- 进程篇(3: 基本进程控制:进程的退出)--请参照本博客“操作系统”专栏
- 进程的基本概念和进程控制
- 进程的基本属性:进程ID、父进程ID、进程组ID、会话和控制终端
- 进程控制(一)基本概念
- Linux系统进程控制编程(一)————基本概念和函数getpid的使用
- 进程篇(4: 基本进程控制:其他相关控制)--请参照本博客“操作系统”专栏
- 进程控制理论<一>---基本概念和进程建立
- linux下的基本操作12(进程控制)
- Linux 进程的基本控制—atexit、on_exit函数、文件锁
- 进程的基本属性:进程ID、父进程ID、进程组ID、会话和控制终端
- 进程控制之exit和waitpid(wait)函数
- Linux进程管理(一、 基本概念和数据结构)
- 进程控制
- (WIFI)远程控制基本流程
- 通过 Mutex/Semaphore 实现程序进程实例的控制
- PHP中利用pcntl进行多进程并发控制
- 进程的五种基本状态 - 操作系统
- 基本概念之二操作系统(计算机管理控制程序OS:Operation System)