1.进程创建
fork—通过复制调用进程创建一个新的子进程:
复制(pcb—代码共享,数据独有)—程序计数器(运行位置也一样)
返回值:父进程返回子进程的pid,子进程返回0
写时拷贝技术
vfork—创建子进程,共用同一个虚拟地址空间,为了防止调用栈混乱,因此父进程调用vfork会阻塞,阻塞到子进程退出或者子进程程序替换,开辟内存创建自己的地址空间。
//子进程推出后,父进程才运行
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int pid = vfork();
if(pid == 0)
{
printf("i am child!\n");
sleep(5);
exit(0);//只释放PCB资源
//return -1; 退出会段错误,因为退出所有资源(包括虚拟地址空间),但是父子进程共用一块虚拟空间,所以当父进程运行时虚拟空间已经被释放。
}
printf("i am parents\n");
return 0;
}
fork/vfork在内核中创建进程都是调用clone实现pcb创建并拷贝数据的;
vfork已经没人使用了,因为vfork的存在意义就是创建子进程
2.进程终止
进程终止也就是进程退出
退出场景:
正常退出:结果符合预期/结果不符合预期
异常退出:常见的程序崩溃
//演示进程退出场景以及如何退出
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int hot_beef_noodle = 1;
int main()
{
printf("---------------");
sleep(3);
_exit(1);//任意位置退出进程,不会刷新缓冲区,printf不会输出
//exit(1) //任意位置退出进程
//return 1;//main函数中的return也是退出进程
/*模拟异常退出
char *ptr = NULL;
memcpy(ptr,"nihao",5);
*/
printf("i want to eat hot_beef noddle\n");
if(hot_beef_noodle){
printf("eat hot_beef noddle\n");
}
else{
printf("i want to eat old noddle\n");
}
printf("want to eat noodle\n");
return 0;
}
如何退出:
main函数中return 退出前刷新缓冲区
void exit(int status) 这是一个库函数 退出前刷新缓冲区
void _exit(int status) 系统调用接口,不会刷新缓冲区,缓冲区数据丢失
库函数和系统调用接口的关系:上下级的封装调用关系
int atexit(void(*function)(void))告诉操作系统进程推出的时候执行一下function函数
3.进程等待
等待子进程的状态改变(等待子进程退出)-获取子进程的退出返回值
为什么要等待子进程退出: 因为子进程退出时为了保存推出原因,因此操作系统不能释放子进程全部资源,因此通知父进程获取子进程退出返值,允许释放资源;但是通常这个操作的通知是静音的,导致父进程没有关注到子进程的退出,因此子进程成为僵尸进程
若父进程获取到了子进程的返回值,僵尸进程将没有存在的意义就会被释放资源
因为不知道子进程何时退出,因此只能创建之后一直等待着子进程的退出
如何等待
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options)
wait接口功能是一直等待子进程退出,子进程退出后,获取到返回值,放到传入的参数statu中,如果一直没有子进程退出,wait函数将一直阻塞。
**阻塞:**为了完成一个功能发起调用,当前若不具备完成条件,等待直到条件具备完成功能后返回
非阻塞为了完成一个功能发起调用,当前若不具备完成条件,则立即报错返回
//实现进程等待的demo,从这个demo中体会wati/waitpid两个接口的阻塞等待,以及子进程的返回值的获取
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include <sys/types.h>
#include <sys/wait.h>
//extern int erron;
int main()
{
int pid = fork();
if(pid < 0)
{
//errno是一个全局变量,存储每次系统调用出现错误原因编号
// //strerror,通过错误编号获取字符串错误原因
printf("fork error:%s\n",strerror(errno));
perror("fork error");
}
else if(pid == 0)
{
sleep(3);
// exit(256);//返回0 257 返回 1,只有低8位
}
/*pid_t wait(int *status);
阻塞等待任意一个子进程退出,获取返回值
*/
wait(NULL);
/*pid_t waitpid(pid_t pid,int *status,int options)
阻塞等待任意一个子进程或者指定的子进程退出
pid: -1 等待任意进程 pid > 0: 等待指定的子进程
options: WNOHANG 设置为非阻塞 0:默认阻塞
返回值: 若WNOHANG被指定,没有子进程退出则立即报错返回0;错误:-1
*/
//waitpid(-1,NULL,0);//阻塞
// while(waitpid(-1,NULL,WNOHANG) == 0) //非阻塞等待
// {
//非阻塞轮询操作
// printf("drink coffee");
// sleep(1);
// }
int statu; //获取退出码
while(waitpid(-1,&statu,WNOHANG) == 0)
{
//非阻塞轮询操作
printf("drink coffee");
sleep(1);
}
if((statu & 0x7f) == 0)
{
printf("exit code%d",(statu >> 8) & 0xff);
}
while(1)
{
printf("i am parent\n");
sleep(1);
}
return 0;
}
while [ true ];do clear; ps -aux || grep -v wait
程序替换
程序替换:替换一个进程正在运行的程序
重新加载一个新的程序到物理内存中,对一个进程的代码段通过页表在物理内存中的地址进行修改映射关系,让程序的代码段经过页表转换后,指向了新的程序位置
让一个进程的PCB通过页表转换映射到物理内存上另一个程序的地址;进程将运行另一个程序,以前的数据和代码都是消除了,
如何替换:exec函数族
extern char **environ;
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 execvpe(const char *file, char *const argv[], char *const envp[]);
带p和不带p区别:要加载的程序是否需要确定给出所在路径
v和l的区别:程序运行参数是函数的参数平铺或者直接组织成为字符串指针数组给出
带e与不带e的区别:要运行的程序,是否需要重新自定义环境变量
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("leihaoa~~~~");
//int execl(const char *path, const char *arg, ...);
//使用PATH这个路径的程序,替换当前进程要运行的程序
//让当前进程运行ls这个程序的功能
//后面arg以及...都是这个程序的运行参数
execl("/usr/bin/ls","ls","-l","-a",NULL);
//execlp("ls","-l","-a",NULL);
//int execv(const char *path, char *const argv[]);
/*char *argv[32] = {NULL};
argv[0] = "ls";
argv[1] = "-l";
argv[2] = "-a";
argv[3] = "NULL";
execv("/usr/bin/ls",argv);*/
printf("nihao~~");//被替换,不执行
}