此篇博客作为上一篇博客的补充
Linux浅谈进程1
进程的创建
进程创建的方式:
- fork()
- vfork()
关于fork()函数
头文件为#include<unistd.h>
pid_t fork(void)
返回值:
子进程返回0,父进程返回子进程id,出错返回-1
进程创建的一般过程
- 给新建的进程分配一个内部的标识符,在内核中分配PCB。
- 复制父进程的环境
- 为进程分配资源(代码,数据,堆栈)
- 父进程地址空间的内容也复制到新的进程空间中
- 将该进程放到就绪队列中。
- 向父进程返回子进程的进程号,对子进程返回0
当一个进程fork出一个子进程后,就有两个二进制代码相同的进程,并且运行到相同的地方,但每个进程都将开始执行自己的代码。
fork()调用失败的原因
- 系统中进程太多了
- 或实际用户的进程数超过了限制(每台电脑都有一个进程的最大数)
- 系统不支持fork()函数,(如windows)
vfork函数
也是用来创建子进程(在fork函数没有出现写时拷贝时出现的,性能高,但是有Bug)
- 子进程一定先于父进程执行。
- 子进程调用exec或者exit之后父进程才能执行
vfork与fork区别
- fork:父子进程交替运行。vfork:子进程运行,父进程阻塞,直到子进程运行结束(创建出子进程,子进程运行完了才会运行父进程),程序最后用exit(0)进程退出
- fork实现了写时拷贝。vfork就算是写也不拷贝
- vfork必须使用exit或excl
- 就算是fork实现了写时拷贝,性能也没有vfork高
- vfork虽然性能高,但是每个系统上的vfork都有问题,不要使用
进程的终止(结束)
进程退出的场景:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
进程常见退出方法
正常终止
正常退出返回码为0
1. 从main返回
2. 调用exit
3. _exit
异常退出:
- ctrl+C
- kill
- abort()
查看进程退出码
可以用echo$?查看进程的退出码
echo $?:查看上一个退出码 退出码返回0~255 只用了int的8个比特位,其余位有别的用途,比如,程序是否是正常退出,如果是异常退出,是什么原因导致异常退出
exit所做的工作
- 执行用户通过atext或on_exit定义的清理函数
- 关闭所有打开的流,所有的缓冲数据均被写入
- 调用_exit.
回调处理函数atexit(函数名):在程序结束前执行。有32个上限,运行顺序与定义顺序相反
_exit与exit区别
- _exit()直接终止,清理缓冲区。
- exit()直接退出程序
- _exit是系统调用,exit最终也会调用_exit
进程的撤销(销毁)
- 关闭软中断
- 回收资源(如关闭文件)
- 将进程的状态置为僵尸态
- 写记账信息
- 转进程调度
进程的等待
进程等待是一个很重要的过程,如果子进程退出,父进程不回收资源,就会造成我们之前博客讲过的“僵尸进程”的问题,从而导致内存泄漏
进程等待就可以很好的解决这个问题
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待方法
- wait
- waitpid
wait
//#include<sys/types.h>
//#include<sys/wait.h> //头文件
pid_t wait(int* status)
返回值:成功返回被等待进程pid,失败返回-1
参数:输出型参数,获取子进程的退出状态,不关心可以设置为NULL
wait:做的三件事
- 阻塞当前进程,直到子进程退出才返回
- 回收子进程残留资源
- 获取子进程退出信息(状态)
waitpid
pid_t waitpid(pid_t pid,int *status,int options);
返回值:
当正常返回时waitpid返回收集到的子进程的进程ID
如果设置了选项WHOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
如果调用中出错,则返回-1,这是errno会被设置成相应的值以指示错误所在
参数:
pid:
pid=-1,等待任一个子进程,与wait等效。
pid >0,等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真(查看进程是否正常退出)
WEXITSTATUS(status):若WEXITSTATUS非零,提取子进程退出码(查看进程的退出码)
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不等待,若正常结束,返回该子进程的ID。
用代码看一下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
{
perror("fork");
exit(1);
}
if(pid==0)
{
int i=0;
printf("this is maomaochong\n",getpid());
sleep(1);
exit(-1);
}
else
{
int status;
pid_t w=wait(&status);
if(w==-1)
perror("wait");
else
printf("parent w=%d\n",w);
if(WIFEXITED(status))
{
printf("exitcode=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("signum=%d\n",WTERMSIG(status));
}
}
}
结果为:
我们通过结果可以看出,进程正常退出。
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
替换函数:
有六种以exec开头的函数,都统称为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[]);
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值
命名解释
- l(list):表示参数采用列表
- v(vector):表示参数采用数组
- p(path):带p的函数会自动搜索环境变量PATH
- e(env):表示自己维护环境变量