进程创建
fork
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork函数返回,开始调度器调度
vfork
创建子进程,与父进程共用同一块虚拟地址空间,但是会出现调用栈混乱。
为了防止调用栈混乱,因此需要父进程调用vfork会阻塞,阻塞到子进程exit退出或子进程程序替换重新开辟内存创建自己的地址空间。
子进程不能用return返回,因为子进程return后会将资源释放,父进程则调用栈混乱。
fork/vfork在内核中创建进程都是调用clone函数实现pcb创建并拷贝数据的。
vfork已经被淘汰了,因为vfork的存在意义就是使创建子进程的效率更高;但是fork通过写时拷贝技术实现了进程创建后,vfork几乎就不用了。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
//pid_t vfork(void);
int pid = vfork();
if (pid == 0)
{
printf("---child---\n");
sleep(5);
exit(0);
}
printf("---parent---\n");
return 0;
}
进程终止(进程退出)
退出场景:
- 正常退出,结果符合预期
- 正常退出,结果不符合预期
- 异常退出:常见的程序崩溃
进程如何退出:
- main函数中return,退出前会刷新缓冲区
- exit,库函数,退出前会刷新缓冲区
- _exit,系统调用接口,直接退出,释放资源,不会刷新缓冲区
- int atexit(void (*function)(void));告诉操作系统进程退出的时候执行一下function函数
进程等待
等待子进程的状态改变(等待子进程退出),获取退出子进程的返回值
为什么要等待子进程退出?
- 因为子进程退出时为了保存退出原因,操作系统不能释放子进程全部资源,因此通知父进程获取子进程退出返回值,允许释放资源;但是通常这个操作是静音的,导致父进程没有关注到子进程的退出,因此子进程称为僵尸进程。
- 若父进程获取了子进程的返回值,僵尸子进程将没有存在的意义了,就会被释放资源。
- 因为不知道子进程何时退出,因此只能创建之后一直等着子进程的退出。
如何等待?
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
pid_ t waitpid(pid_t pid, int* status, int options);
参数:
pid:
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值
options:
WNOHANG:将waitpid设置为非阻塞,即使没有子进程,它也会立即返回
0:默认阻塞
返回值:
当正常返回的时候,waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
status:
WIFEXITED(status):如果子进程正常结束则为非0值(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
wait接口功能是一直等待任意一个子进程退出,子进程退出后,获取到返回值,放到传入的参数status中,如果一直没有子进程退出,wait函数将一直阻塞。
阻塞:为了完成功能发起调用,如果当前不具备完成条件,则一直等待,直到完成后返回
非阻塞:为了完成功能发起调用,如果当前不具备完成条件,则立即报错返回
子进程退出返回值获取:(status >> 8) & 0xff
- int的4个字节中高16位没有使用,低16位中的高8位存储子进程退出返回值
- 低16位中低8位中的高1位存储core dump标志;低7位存储异常退出信号值
- core dump标志:即核心转储,程序异常退出时保存程序的运行信息。
判断程序是否正常退出:status & 0x7f -->异常信号值为0,程序正常退出;否则异常退出
WIFEXITED (status):判断进程是否正常退出,正常退出返回true
WEXITSTATUS(status): 在进程正常退出时,获取子进程退出码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
int pid = fork();
if (pid < 0)
{
//error是一个全局变量,存储每次系统调用出现错误原因编号
//strerror,通过错误编号获取字符串错误原因
printf("fork error:%s\n", strerror(errno));
//perror直接打印上一个系统调用错误原因
perror("fork error\n");
}
else if (pid == 0)
{
sleep(3);
exit(1);
}
//pid_t wait(int *status);
//阻塞等待任意一个子进程退出,获取返回值
//wait(NULL);
//pid_t waitpid(pid_t pid, int *status, int options);
//阻塞等待任意一个子进程或者指定的子进程退出
//pid = -1:等待任意一个子进程; pid > 0:等待指定子进程
//options:WNOHANG,将waitpid设置为非阻塞 0:默认阻塞
//返回值:若WNOHANG被指定,没有子进程退出则立即报错返回0;错误为-1;
//pid>0,此时的pid是子进程的pid,因为父进程返回的是子进程的pid
//也就是waitpid函数指定等待子进程退出
int status;
while (waitpid(pid, &status, WNOHANG) == 0)
{
//非阻塞轮询操作
printf("drink coffee\n");
sleep(1);
}
if ((status & 0x7f) == 0)
{
printf("exit code:%d\n", (status >> 8) & 0xff);
}
//子进程正常退出,返回非0
if (WIFEXITED (status))
{
//获取子进程的退出码
printf("exit code:%d\n", WEXITSTATUS(status));
}
while(1)
{
printf("---parent---\n");
sleep(1);
}
return 0;
}
程序替换
简单来说就是替换一个进程正在运行的程序。
重新加载一个新的程序到物理内存中,对一个进程的代码段通过页表在物理内存中的地址进行修改映射,让程序的代码段经过页表转换后,指向新的程序位置。
让一个进程pcb通过页表转换映射到物理内存上另一个程序的地址;进程将运行一个程序;以前的数据和代码都失效了,因此需要重新初始化页表代码段数据段。
如何进行程序替换?exec函数族
作用:用exec函数族加载的程序文件代替该进程原来的程序文件。
只有execv()是真正意义上的系统调用,其他的都是在此基础上经过包装的库函数。
带p和不带p的区别:要加载的程序是否需要确定给出所在路径
v和l的区别:程序的运行参数是函数的参数平铺或者直接组织成为字符串指针数据给予
带e和不带e的区别:要运行的程序是否需要重新自定义环境变量
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world!\n");
//int execl(const char *path, const char *arg, ...);
//使用path这个路径的程序,替换当前进程要运行的程序
//让当前进程运行ls这个程序的功能
//后边arg以及...都是这个程序的运行参数
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
//int execlp(const char *file, const char *arg, ...);
//execlp("ls", "ls", "-l", "-a", NULL);
//int execv(const char *path, char *const argv[]);
//char* new_argv[] = {"ls", "-l", "-a", NULL};
//execv("/usr/bin/ls", new_argv);
//int execle(const char *path, const char *arg, ..., char *const envp[]);
//execle("/usr/bin/ls", "ls", "-l", NULL, NULL);
printf("world hello!\n");
return 0;
}