文章目录
前言
本文为笔者学习笔记,若有不妥之处,欢迎斧正。
一、进程相关概念
进程的定义:“进程”是操作系统的最基本、最重要的概念之一。但迄今为止对这一概念还没有一个确切的统一的描述。下面给出几种对进程的定义描述。进程是程序的一次执行。进程是可以并行执行的计算。进程是一个程序与其使用的数据在处理机上顺序执行时发生的活动。进程是程序在一个数据集合上的运行过程。它是系统进行资源分配和调度的一个独立单位。
进程的特征:动态性:是程序的一次执行;并发性:进程是可以并发执行;独立性:是系统进行资源分配和调度的一个独立单位;异步性:进程间的相互制约,使进程执行具有间隙;结构性:进程是具有结构的。
- 什么是程序?什么是进程?两者有什么区别呢?
程序:是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具。
进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
区别:
- 程序会一直存储在磁盘中,进程是暂时的,是程序在内存中执行的过程,可以创建和撤销。
- 程序是静态的,而进程是动态的
- 进程具有并发性,而程序没有
- 进程是竞争计算机资源的最小单位,而程序不是
- 进程和程序不是一一对应的关系,一个程序可以有多个进程
- 什么是进程标识符?
进程标识符:每个进程都有一个非负整型表示的唯一进程ID。
注意:虽然唯一,但是进程ID是可复用的。
可与使用getpid()获取自身的进程标识符,getppid()获取父进程的标识符。
- 什么是父进程?什么是子进程?
当进程A创建了进程B,那么A就是父进程,B就是子进程。
- C程序存储空间分配?
正文段:由CPU执行的机器指令部分。通常正文段是可共享的,所以即使是频繁执行的程序,在存储器中也只有一个副本,另外正文段常常是只读权限,以防止程序由于意外而修改其自身的指令。
初始化数据段:通常将称为数据段,包含了程序中明确赋予了初值的变量(使变量带有初始值,并存储在数据段中)。
非初始化数据段:通常称为bss段,名称来源是早期的汇编运算符(block started symbol),在程序开始执行之前,内核将此段中的数据初始化为0或者空指针。
堆:通常在队中进行动态存储分配 。
栈:自动变量以及每次函数调用时所需要的保存的信息都存放在此段中。
二、进程相关API详解
1、fork函数
函数原型:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
函数作用:一个现有的进程可以调用fork函数创建一个新进程(子进程)。
参数说明:无参。
返回值:
成功:返回两次。一次是子进程返回0,父进程返回子进程的ID。
失败:返回-1。
说明:fork()函数创建进程相当于把父进程做了一个写时复制,并且给了子进程去执行。
进程的数据使用是指针,不是真实物理内存地址,
父进程fork一个子进程后,父进程和子进程的指针指向的是相同的内存地址,
这样子进程就获得了父进程的全部数据,
父进程和子进程是隔离的,父子进程遵循写时复制原则,对修改数据相互没有影响。
fork 有以下两种用法:
- 父进程希望复制自己,使父进程和子进程执行不同的代码段。应用场景:网络服务进程中——父进程等待客户端的服务请求。当请求到达时,调用fork使子进程处理请求,父进程继续等待下一个服务请求。
- 一个进程要执行一个不同的程序。这是shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec族函数。
注意事项:fork函数创建的父子进程,执行顺序是不确定的,两者相互争夺CPU资源
2、vfork函数
函数原型:
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
函数作用:一个现有的进程可以调用fork函数创建一个新进程(子进程)。
参数说明:无参。
返回值:
成功:返回两次。一次是子进程返回0,父进程返回子进程的ID。
失败:返回-1。
vfork函数和fork函数的区别:
- vork直接使用父进程的存储空间,不拷贝。
- vork函数保证子进程先运行,并且当子进程代用exit函数退出后,父进程才开始运行。
- vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在 调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
3、进程退出函数
函数原型:
#include <unistd.h>
#include <stdlib.h>
void exit(int status);
void _exit(int status);
void _Exit(int status);
函数作用:进程退出。
参数说明:子进程状态(目的是为了通知父进程)
返回值:无。
进程正常退出:
- return
- exit(),属于标准C库
- _exit(),_Exit(),属于系统调用
- 进程的最后一个线程在其启动例程中执行return。
- 进程的最后一个线程调用pthread_exit 函数。
进程异常退出:
- 调用abort()
- Linux中强制终止进程(ctrl + c)
- 线程对取消请求作出响应
4、进程等待函数
4.1wait函数
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
函数作用:父进程等待子进程退出,并收集子进程的退出状态。
参数说明:
- wstatus是一个整型指针,用来存放子进程退出时的状态,此时非空。若不关心退出状态,此时为空。
返回值分三种情况:
- -1: 调用函数出错
- 0: 若waitpid的options设置了WNOHANG,且调用中没有子进程退出,立即返回0
- 大于0:返回退出进程的ID
4.2waitpid函数
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
函数作用:父进程等待子进程退出,并收集子进程的退出状态。
参数说明:
- 等待进程的ID(监听进程的ID)
- wstatus是一个整型指针,用来存放子进程退出时的状态,此时非空。若不关心退出状态,此时为空。
- 提供一些额外的选项控制waitpid,目前linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数宏,可以使用 | 连接使用。
参数作用解释:
- 参数pid>0时,只等待进程ID等于pid的子进程,只要指定的子进程没有结束,waitpid就会一直等待
- 参数pid=0时,等待同一个进程组中的任一子进程,如果子进程已经加入别的进程组,waitpid不会对其进行处理
- 参数pid=-1时,等待任何一个子进程退出,与wait作用一样
- 参数pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的id等于pid的绝对值
返回值分三种情况:
- -1: 调用函数出错
- 0: 若waitpid的options设置了WNOHANG,且调用中没有子进程退出,立即返回0
- 大于0:返回退出进程的ID
4.3wait和waitpid的联系
wait实质上是waitpid中pid=-1,options=0时封装,即wait(&status)与waitpid(-1, &status, 0)完全相同。
三、僵死进程和孤儿进程
1、僵死进程
定义:如果子进程退出的状态没有被父进程所收集,那么子进程就变成了僵死进程(僵尸进程)。
子进程退出返回的状态需要使用特殊的宏定义来解析。
详细例程和宏定义的使用,见后面的代码演示。
避免出现僵死进程:
- 父进程调用 wait 或 waitpid 来收集资源
- 用 kill 指令杀死父进程 语法:kill -9 pid
- 通过信号机制,在处理函数中调用wait,回收资源
2、孤儿进程
定义:当调用fork和vfork后,就有父进程和子进程,如果两个进程都在运行,但是父进程比子进程先结束,导致只有子进程在运行时,此时的子进程就是孤儿进程。(简单来说就是父进程死了,子进程就成了孤儿进程)
注意事项:只有在父进程结束后,子进程才会变成孤儿进程
Linux系统中,为了避免出现过多的孤儿进程,init进程会收留孤儿进程,编程孤儿进程的父进程。(可以理解为继父)
被init进程收养后,会有操作系统来处理孤儿进程。
四、代码演示
- 获取进程pid
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = getpid();
printf("pid=%d\n",pid);
getchar();
return 0;
}
- 验证fork函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = getpid();
fork(); //调用后,下面的代码段都会被父子进程执行
while(1)
{
if(pid == getpid())
{
printf("this is father pid,pid=%d\n",getpid());
}
else
{
printf("this is son pid,pid=%d\n",getpid());
}
sleep(2);
}
return 0;
}
运行结果:
this is father pid,pid=3745 //不仅验证了fork,也验证了父进程和子进程的执行顺序不确定
this is son pid,pid=3746 //两个进程会争夺CPU资源
this is son pid,pid=3746
this is father pid,pid=3745
this is father pid,pid=3745
this is son pid,pid=3746
this is son pid,pid=3746
this is father pid,pid=3745
this is son pid,pid=3746
- 验证 fork函数 的返回值
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
while(1)
{
if(pid > 0)
{
printf("this is father pid,pid=%d\n",getpid());
}
else
{
printf("this is son pid,pid=%d\n",getpid());
}
sleep(2);
}
return 0;
}
运行结果:
this is father pid,pid=3775
this is son pid,pid=3776
this is father pid,pid=3775
this is son pid,pid=3776
this is father pid,pid=3775
this is son pid,pid=3776
- 验证fork的写实复制
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
int data = 10;
pid = fork();
printf("pid=%d\n",pid);
if(pid > 0)
{
printf("this is father pid,pid=%d\n",getpid());
}
else if(pid == 0)
{
printf("this is son pid,pid=%d\n",getpid());
data+=100;
}
printf("data:%d\n",data);
return 0;
}
运行结果如下:
pid=3906
this is father pid,pid=3905
data:10
pid=0
this is son pid,pid=3906
data:110
- fork 的应用模板
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid > 0) //父进程执行的代码段
{
while(1)
{
printf("this is father pid,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid == 0) //子进程执行的代码段
{
while(1)
{
printf("this is son pid,pid=%d\n",getpid());
sleep(1);
}
}
return 0;
}
- 验证 fork 和 vfork 的区别
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
int *data=(int *)malloc(sizeof(int));
*data = 0;
pid = vfork();
if(pid > 0)
{
while(1)
{
printf("this is father pid,pid=%d\n",getpid());
printf("data:%d\n",*data);
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is son pid,pid=%d\n",getpid());
(*data)++;
if(*data == 5)
{
exit(0);
}
sleep(1);
}
}
return 0;
}
运行结果如下:
this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is son pid,pid=3955
this is father pid,pid=3954
data:5
this is father pid,pid=3954
data:5
this is father pid,pid=3954
data:5
- wait等待子线程退出
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int data=-1;
pid = fork();
if(pid > 0)
{
wait(NULL);
while(1)
{
printf("this is father pid,pid=%d\n",getpid());
printf("data:%d\n",data);
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is son pid,pid=%d\n",getpid());
data++;
if(data == 5)
{
exit(3);
}
sleep(1);
}
}
return 0;
}
运行结果如下:
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is son pid,pid=4269
this is father pid,pid=4268
data:-1
this is father pid,pid=4268
data:-1
- wait等待线程退出,并收集子线程的退出状态
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int data=-1;
pid = fork();
if(pid > 0)
{
wait(&data);
while(1)
{
printf("this is father pid,pid=%d\n",getpid());
printf("data:%d\n",WEXITSTATUS(data));
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is son pid,pid=%d\n",getpid());
data++;
if(data == 5)
{
exit(3);
}
sleep(1);
}
}
return 0;
}
运行结果如下:
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is son pid,pid=4303
this is father pid,pid=4302
data:3
this is father pid,pid=4302
data:3
this is father pid,pid=4302
data:3
- waitpid的使用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int data=-1;
pid = fork();
if(pid > 0)
{
waitpid(pid,&data,WNOHANG);
while(1)
{
printf("this is father pid,pid=%d\n",getpid());
printf("data:%d\n",WEXITSTATUS(data));
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is son pid,pid=%d\n",getpid());
data++;
if(data == 5)
{
exit(3);
}
sleep(1);
}
}
return 0;
}
运行结果如下:
this is father pid,pid=4319
data:255
this is son pid,pid=4320
this is father pid,pid=4319
this is son pid,pid=4320
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is father pid,pid=4319
this is son pid,pid=4320
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is son pid,pid=4320
this is father pid,pid=4319
data:255
this is father pid,pid=4319
data:255
后期会补充一些相关的案例和文中没有涉及到的知识。
参考资料:
《UNIX环境高级编程》 第三版