一、进程创建
1、fork函数:
可以在代码中创建一个子进程。
pid_t fork(void)
- a. 没有参数
- b. pid_t:·本质是整形
- c. 原理:fork创建的子进程的PCB拷贝父进程的PCB
- d. 返回值:
创建失败:-1 创建成功:>0(子进程的PID号):返回给父进程 =0:返回给子进程
注意:
- 子进程是拷贝父进程的PCB的,子进程的大部分数据来源于父进程,例如:内存指针(数据段、代码段)
- 父进程创建子进程成功之后,父子进程是两个独立的进程(进程的独立性),父子进程的调度取决于操作系统内核
- 进程是抢占式执行的,父子进程谁先运行是不能确定的
- 写时拷贝:写时拷贝是一种可以推迟甚至免除拷贝数据的技术。 内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。 只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。 也就是说, 资源的复制只有在需要写入的时候才进行 ,在此之前,只是以只读方式共享。
2、vfork函数:
父进程与子进程的内存指针指向同一块进程虚拟地址空间 调用方法:与fork函数完全相同
- vfork函数调用栈混乱的问题:子进程调用函数不出栈,则父进程调用函数也出不了栈
- 解决方法:子进程先调用,子进程调用完毕之后,父进程再调用
二、进程终止
exit函数是库函数,在调用时会刷新缓冲区; 而_exit函数是库函数,在调用时不会刷新缓冲区。
三、进程等待
1、进程等待的作用:
父进程进行进程等待,子进程在先于父进程退出之后,由于父进程在等待子进程,所以父进程会回收子进程的退出资源,从而防止子进程产生僵尸进程。
2、wait函数:
pid_t wait(int *status)
- 返回值:成功返回子进程的pid号;失败返回-1
- 参数:整形指针,4个字节中只使用到2个字节。在这2个字节中,前8个比特位为进程退出码,中间一位为coredump标志位,后7位为终止信号。
见下图:
coredump标志位: 取值为1:表示当前的进程是由coredump(核心转储文件)产生 取值为0:表示当前进程没有coredump产生
终止信号:当前程序是由什么信号导致的终止
参数int *status是一个出参: 调用者准备一个int型的变量,将地址传给wait函数,wait函数在自己实现的内部进行赋值,当wait函数返回后,调用者就可以通过int变量,获取进程退出的信息。
下面对wait函数进行测试,看调用wait函数之后,子进程是否还会产生僵尸进程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid=fork();
if(pid<0)
{
printf("create process failed\n");
return 0;
}
else if(pid==0)
{
printf("i am child, %d-%d\n", getpid(), getppid());
exit(1);
}
else
{
int status;
wait(&status);
while(1)
{
printf("i am father, %d-%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
运行结果如下: 我们在输入子进程的PID号后发现子进程并未产生僵尸进程。
那么如何确定子进程是因为父进程进程等待而未产生僵尸进程还是因为在父进程退出后被1号进程所领养而未产生僵尸进程呢?
我们将代码进行修改,在子进程的代码中加入while循环,使子进程不结束,观察父进程是否退出,如父进程仍在运行,则说明是wait函数使子进程未产生僵尸进程。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid=fork();
if(pid<0)
{
printf("create process failed\n");
return 0;
}
else if(pid==0)
{
printf("i am child, %d-%d\n", getpid(), getppid());
while(1)
{
sleep(1);
}
exit(1);
}
else
{
int status;
wait(&status);
}
return 0;
}
代码运行结果如下: 通过上图可以发现,在子进程未退出时,父进程由于调用wait函数仍在运行,从而验证得到是wait函数使子进程未产生僵尸进程。我们称这个现象为阻塞。
阻塞:当前执行流调用某一个函数的时候,该函数一直不返回,称这个现象为阻塞。
3、waitpid函数:
pid_t waitpid(pid_t pid,int *status,int options)
用代码实现waitpid函数:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid < 0)
10 {
11 perror("fork");
12 return 0;
13 }
14 else if(pid == 0)
15 {
16 printf("i am child, pid is %d, ppid is %d\n", getpid(), getppid());
17 sleep(5);
18 exit(1);
19 }
20 else
21 {
22 pid_t ret = 0;
23 do
24 {
25 ret = waitpid(-1, NULL, WNOHANG);
26 }while(ret == 0);
27 sleep(20);
28 printf("i am father, pid is %d\n, exit...", getpid());
29 }
30 }
四、进程程序替换
1、作用:将正在运行的进程,替换成另外一个程序,运行另外一个程序的代码。
2、原理:将进程的代码段和数据段替换成为新的程序的代码和数据,更新堆栈信息。 3、应用场景: ①守护进程 ②bash(命令行解释器)
4、接口: exec接口簇
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[]);
总结:①函数名称带有“p”,则表示当前函数会自动搜索环境变量PATH ②函数名称带有“e”,则表示当前函数需要自己组织环境变量,放到envp指针数组中,以NULL结尾 ③函数名称带有“l”,则表示给待替换的可执行程序传递的命令行参数为可变参数列表 ④函数名称带有“v”,表示给待替换的可执行程序传递的命令行参数为指针数组
|