进程
fork,创建一个子进程。
失败返回-1;成功时:父进程fork()的返回值是子进程的进程ID,子进程fork()返回0
根据返回值判断fork之后是子进程还是父进程。注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
用fork创建一个子进程。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t pid;
printf("-----start------\n");
//父进程fork()的返回值是子进程的进程ID
//子进程fork()返回0,根据返回值判断fork之后是子进程还是父进程。
pid = fork();
if (pid > 0)
{
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
}
else
{
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
}
sleep(1);
printf("pid=%d\n", pid);
printf("-----end------\n");
return 0;
}
父进程用fork创建n个子进程。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t pid;
int i = 0;
for(;i<5;i++)
{
pid = fork();
if (pid > 0)
{
//父进程
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
}
else
{
//子进程
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
//如果子进程不在这里break的话,当下一次for的时候,子进程也会fork出他自己的一个子进程
//这样总共就会有32个进程了
break;
}
}
while(1);
return 0;
}
for在子进程分支时break。
用while来实现 创建n个子进程。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t pid;
int i = 0;
while (pid = fork())
{
//父进程
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
i++;
if (i==5)
{
break;
}
}
//子进程
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
while(1);
return 0;
}
进程共享:读时共享,写实复制
父子进程之间在fork后。有哪些相同,那些相异之处呢?
刚fork之后:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
僵尸进程和孤儿进程:
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程例子
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if ( pid == 0 )
{
//子进程
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
sleep(2);
}
else if (pid > 0)
{
//父进程
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
while(1)
{
sleep(1);
}
}
else
{
perror("fork error");
exit(1);
}
return 0;
}
结束僵尸进程的父进程可以结束僵尸进程。
孤儿进程例子
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if ( pid == 0 )
{
//子进程
while (1)
{
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
sleep(2);
}
}
else if (pid > 0)
{
//父进程
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
sleep(5);
}
else
{
perror("fork error");
exit(1);
}
return 0;
}
通过 kill -9 130876 结束孤儿进程
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
int state;
pid_t pid = fork();
if (pid == 0)
{
//子进程
printf("子进程,pid=%d,ppid=%d\n", getpid(), getppid());
sleep(25);
printf("子进程退出\n");
//return 和 exit结果都一样的
//exit(100);
return 101;
}
else if (pid > 0)
{
//父进程
printf("父进程,pid=%d,ppid=%d\n", getpid(), getppid());
//等待子进程退出
//pid_t wpid = wait(NULL);
pid_t wpid = wait(&state);
if (WIFEXITED(state))
{
printf("子进程退出状态%d\n",WEXITSTATUS(state));
}
if (WIFSIGNALED(state))
{
printf("终止子进程的信号%d\n",WTERMSIG(state));
}
printf("子进程%d退出\n", wpid);
printf("父进程退出\n");
}
else
{
perror("fork error");
exit(1);
}
return 0;
}