储备知识
1.什么是程序?什么是进程?有什么区别?
程序是静态的概念,gcc xxx.c -o pro 磁盘中生成pro文件,叫做程序。
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程。
2.如何查看系统中的有哪些进程?
(1)使用ps指令查看 ps -aux|grep a.out
实际工作中,配合grep来查看程序中是否存在某一个进程。
(2)使用top指令查看,类似windows任务管理器。
3.什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做PID,类似身份证。
Pid=0:称为交换进程(swapper)
作用—进程调度
Pid=1:init进程
作用—系统初始化
4、什么叫父进程?什么叫子进程?
进程A创建了进程B。那么A叫做父进程,B叫做子进程。
5、C程序的存储空间是如何分配?
地址 | 存储空间 | 说明 |
---|---|---|
高地址 | 命令行参数和环境变量(argv等) | |
栈 | 返回调用函数的地址以及调用者的环境信息和局部变量 | |
堆 | 通过malloc等函数申请的动态空间 | |
非初始化数据段(Bss段) | 未初始化的数据,由exec初始化为0 | |
初始化数据段 | 在任何函数外初始化过的变量,由exec从程序文件中读入 | |
低地址 | 正文(代码段) | 由CPU执行的机器指令部分,由exec从程序文件中读入 |
API介绍
一、获取当前进程的PID
pid_t getpid(void);
头文件:
#include <sys/types.h>
#include <unistd.h>
参数:无
返回:调用成功,返回当前进程的PID。
二、获取父进程的PID
pid_t getppid(void);
头文件:
#include <sys/types.h>
#include <unistd.h>
参数:无
返回:调用成功,返回父进程的PID。
三、创建子进程
pid_t fork(void);
头文件:
#include <unistd.h>
参数: 无
返回:调用成功,返回两次。返回值为0,代表当前进程为子进程。返回值为非负数,即子进程的PID,代表当前进程为父进程。调用失败,返回-1。
pid_t vfork(void);
头文件:
#include <sys/types.h>
#include <unistd.h>
参数: 无
返回:调用成功,返回两次。返回值为0,代表当前进程为子进程。返回值为非负数,即子进程的PID,代表当前进程为父进程。调用失败,返回-1。
fork与vfork有什么区别?
1、vfork直接使用父进程存储空间,不拷贝。
2、vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
例程:验证vfork与fork的区别。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid1,pid2;
int cnt=0;
pid1=getpid();
printf("now process pid is %d\n",pid1);
pid2=vfork();
if(pid2==0)
{
while(1)
{
printf("this is son process,PID=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
exit(0);
}
}
else
{
printf("this is father process,PID=%d,son PID=%d\n",getpid(),pid2);
printf("cnt:%d\n",cnt);
}
return 0;
}
三、终止当前进程
void exit(int status);
头文件:
#include <stdlib.h>
参数:退出状态码
返回:无
void _exit(int status);
头文件:
#include <unistd.h>
参数:退出状态码
返回:无
void _Exit(int status);
头文件:
#include <stdlib.h>
参数:退出状态码
返回:无
例程:通过fork函数创建子进程,分别在父进程与子进程中输出当前进程PID,并在父进程中输出子进程PID。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid1,pid2;
pid1=getpid();
printf("now process pid is %d\n",pid1);
pid2=fork();
if(pid2==0)
{
printf("this is son process,PID=%d\n",getpid());
}
else
{
printf("this is father process,PID=%d,son PID=%d\n",getpid(),pid2);
}
return 0;
}
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆、栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write,COW)技术。这些区域由父、子进程共享,使用内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。可以通过下面的例程来验证一下父、子进程的数据空间相互独立。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid1,pid2;
int data=10;
pid1=getpid();
printf("now process pid is %d\n",pid1);
pid2=fork();
if(pid2==0)
{
printf("this is son process,PID=%d\n",getpid());
data+=10;
}
else
{
printf("this is father process,PID=%d,son PID=%d\n",getpid(),pid2);
}
printf("data:%d pid:%d\n",data,getpid());
return 0;
}
可以从打印结果看到,父进程中的data变量保持不变,子进程中的data变量发生了变化。
那么,fork创建一个子进程的一般目的有哪些呢?
(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络进程中是常见的—父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
(2)一个进程要执行不同的程序。这对shell是最常见的情况,这种情况下,子进程从fork返回后立即调用exec。
针对第一点,编写程序来模拟下网络进程,当输入整数1时,表示接收到网络请求,服务器端创建子进程,做出间隔1s循环输出字符串"do net request"的处理,当输入其他整数时,输出字符串"do nothing!",并继续等待网络请求。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int data=0;
pid_t pid;
while(1)
{
scanf("%d",&data);
if(data==1)
{
pid=fork();
if(pid==0)
{
while(1)
{
printf("do net request,PID=%d\n",getpid());
sleep(1);
}
}
}
else
{
printf("do nothing!\n");
}
}
return 0;
}
进程正常退出
1、Main函数调用return
2、进程调用exit(),标准c库
3、进程调用_exit()或者_Exit(),属于系统调用
4、进程最后一个线程返回
5、最后一个线程调用pthread_exit
进程异常退出
1、调用abort
2、当进程收到某些信号时,如ctrl+C
3、最后一个线程对取消(cancellation)请求做出响应。
不管进程如何终止,最后都会执行内核中的同一段代码。这种代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对于三个终止函数(exit、_exit、_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
四、等待子进程结束并收集退出状态
pid_t wait(int *status);
头文件:
#include <sys/types.h>
#include <sys/wait.h>
参数:退出状态码的地址
返回:被终止的子进程PID
表 检查wait和waitpid所返回的终止状态的宏
宏 | 说明 |
---|---|
WIFEXITED(status) | 若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送给exit、_exit或_Exit参数的低8位 |
WIFSIGNALED(status) | 若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使子进程终止的信号编号。另外,有些实现定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真 |
例程:父进程通过wait函数阻塞等待子进程结束,并收集退出状态码,并通过宏WIFEXITED判断是否为正常退出,若正常退出,则为真。通过宏WEXITSTATUS取子进程传送给exit、_exit、_Exit参数的低8位。注意不能直接获取wait的参数。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid1,pid2;
int cnt=0;
int status;
pid1=getpid();
printf("now process pid is %d\n",pid1);
pid2=fork();
if(pid2==0)
{
while(1)
{
printf("this is son process,PID=%d\n",getpid());
sleep(1);
cnt++;
if(cnt==3)
exit(3);
}
}
else
{
wait(&status);
if(WIFEXITED(status))
{
printf("status:%d\n",WEXITSTATUS(status));
}
printf("this is father process,PID=%d,son PID=%d\n",getpid(),pid2);
printf("cnt:%d\n",cnt);
}
return 0;
}
什么是僵尸进程?
僵尸进程是指一个已经终止、但是其父进程尚未对其进行善后处理获取终止进程的有关信息的进程,这个进程被称为“僵尸进程”(zombie)。
怎么查看僵尸进程?
利用命令ps,可以看到有父进程ID为1的进程是孤儿进程;s(state)状态为Z的是僵尸进程。注意:孤儿进程(orphan process)是尚未终止但已停止(相当于前台挂起)的进程,但其父进程已经终止,由init收养;而僵尸进程则是已终止的进程,其父进程不一定终止。
怎样来清除僵尸进程?
1、改写父进程,添加wait函数获取退出状态,在子进程死后要为它收尸。
2、把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程,关机或重启后所有僵尸进程都会消失。
什么是孤儿进程?
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多的孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
例程:编写程序来验证一下孤儿进程的父进程为init进程(进程号为1)。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid1,pid2;
int cnt=0;
pid1=getpid();
printf("now process pid is %d\n",pid1);
pid2=fork();
if(pid2==0)
{
while(1)
{
printf("this is son process,PID=%d,parent PID=%d\n",getpid(),getppid());
//sleep(1);
cnt++;
if(cnt==3)
exit(3);
}
}
else
{
printf("this is father process,PID=%d,son PID=%d\n",getpid(),pid2);
}
return 0;
}