目录
一、进程创建
1.pid_t fork(void);
2.pid_t vfork(void);
3.写时拷贝技术
4.进程拥有独立性——各有各的虚拟地址空间,映射各自数据存储
5.返回值
二、进程终止
1.进程常见退出方法
2.进程退出场景
三、进程等待
进程等待的方法
1.int wait (int* status); [status:输出型参数——用于获取退出子进程的返回值]
2.int waitpid (int pid, int *status, int options);
3.获取子进程status (一个进程退出返回值只用了一个字节来保存)
四、进程程序替换
创建一个进程就是创建一个pcb,pcb在Linux下是一个task_struct结构体,放在内核中,只能通过调用接口实现创建
通过复制父进程创建一个子进程(复制了父进程pcb中的数据)
代码共享,数据独有
也是创建一个子进程,但是父进程使用vfork创建后,vfork的调用并不会立即返回(通常说会阻塞父进程),而是让子进程先运行,直到子进程退出或运行程序替换之后父进程才能运行(vfork创建的子进程若程序替换,调度运行新的程序,就会给自己开辟新的空间,与新的程序建立映射关系)
数据共用
vfork创建的子进程特殊的地方:父子进程共用一个虚拟地址空间(父进程的虚拟地址空间)
在程序运行中每调用一个函数就会有一次函数压栈——函数调用栈
父子进程使用了同一个栈,若父子进程同时运行会造成调用栈混乱,因此让子进程先运行,直到子进程退出或程序替换后有了自己的地址空间
注意:vfork创建的子进程不能在main函数中使用return退出,因为子进程使用return退出释放了所有资源,父进程运行的时候资源是错误的
子进程创建出来后,与父进程映射关系访问同一块物里内存(但虚拟地址空间及页表这些信息都是进程独有),当物里内存中数据即将发生改变时,重新为子进程开辟物理内存,拷贝数据过去
进程之间没有交叉关系,不会受到其他进程运行影响,就是为了保证进程的稳定运行
子进程中返回0;父进程中返回子进程的pid;出错返回-1
(1)main函数中的return
普通函数中的return只能退出函数,不能退出进程
(2)void exit (int status);
在任意位置调用程序的任意位置退出一个进程
(3)void _exit (int status);
在任意位置调用程序的任意位置退出一个进程
问:exit和_exit的区别是什么?
答:exit是库函数,退出时会刷新缓冲区,将缓冲区的数据写入文件;
_exit是系统调用接口,退出进程时直接释放资源,不会刷新缓冲区
正常退出:进程运行到return或exit的地方退出
异常退出:程序运行到中途崩溃了
不管子进程正常退出或者异常退出,只要是退出了,没有被父进程等待处理,就都会成为僵尸进程
父进程等待子进程退出,为了获取退出子进程的返回值,释放退出子进程的所有资源,避免产生僵尸进程
僵尸进程产生的原因:子进程先于父进程退出,为了保存退出返回值而无法完全释放资源产生的
处理退出的子进程,如果调用这个接口没有子进程已经退出,则会使父进程阻塞等待,直到有子进程退出
阻塞:为了完成一个功能我们发起一个调用,但是若当前不具备完成功能的条件,则调用等待
非阻塞:为了完成一个功能我们发起一个调用,但若当前不具备完成功能的条件,则调用立即报错返回
返回值:成功返回处理的退出子进程的pid,失败(比如没有子进程)返回-1
返回值:成功返回退出子进程的pid,没有子进程退出返回0,失败(比如没有子进程)返回-1
wait与waitpid的区别:
(1)wait等待的是任意一个子进程的退出(wait是一个父进程假设有很多子进程,任意一个退出,都会处理后调用返回);waitpid可以等待指定的子进程,也可以等待任意一个子进程,通过第一个参数确定(第一个参数pid == -1表示等待任意)
(2)wait是一个阻塞接口(wait如果没有子进程退出,则会一直等待);waitpid可以默认阻塞,也可以设置为非阻塞,通过第三个参数确定(option == 0表示默认阻塞,option == WNOHANG表示非阻塞),非阻塞操作通常需要循环处理
一个进程退出场景有两种:正常退出、异常退出
一个进程只有正常退出的时候返回值才有意义;若进程是异常退出,则返回值没有意义
因此在获取返回值之前,应先通过低七位判断进程是否是正常退出(正常退出则异常信号值为0;否则大于0)
判断一个程序是否正常退出:取出低七位;status & 0x7f == 0 正常退出 [WIFEXITED()]
如何取出返回值(低16位中的高8位):(status >> 8)& 0xff ——因为返回值只用了一个字符,因此进程的返回值 [WEXITSTATUS]
系统调用接口出错后,如何获取错误原因?
#include
#include
char *strerror(errno); 根据错误编号获取文字信息——错误都是上一次系统调用接口使用错误的原因
perror(char *msg);直接打印上一次系统调用接口使用错误的原因
core dump——核心转储
指的是程序异常退出时,将退出前的程序运行信息保存下来(默认是关闭的)
重新加载另一个程序到内存中,然后将现有的一个pcb的内存指针所指向的内存空间指向这个新的程序(更新页表映射信息),则这个现有的pcb就跑去调度这个新的程序了。
程序替换,给一个进程替换一个新的要调度运行的程序,并且因为这个进程调度的程序已经被替换,因此当运行完毕新的程序后会退出;原先的程序在程序替换以后的代码都不会被运行到(替换后相当于已经没有当前的代码了,只有新的程序)
如何替换:exec函数族
#include
int execl(const char *path, const char *arg, ...);//path—带路径的程序文件名称;arg/...表示程序的运行参数,逐个赋予,最终以NULL结尾
int execlp(const char *file, const char *arg, ...);//PATH环境变量指定了一些路径,execlp会去PATH环境变量指定的路径下查找程序文件
int execle(const char *path, const char *arg, ..., char *const envp[]);//通过envp参数自定义环境变量
int execv(const char *path, char *const arg[]);
int execvp(const char *file, char *const arg[]);
int execve(const char *path, char *const arg[], char *const envp[]);
l和 v的区别:程序运行参数的赋予方式不同
有没有p的区别:新的程序文件的名称是否需要带路径
有没有e的区别:是否自定义环境变量