1. 什么是进程?
- 进程是一个运行着的程序,它包含了程序在运行时的各个资源,进程是linux进行调度的基本单位,也是一个程序运行的基本单位。
- 进程就好比多任务,在我们编程中,进程就好比多个main在同时执行.
2. 进程状态
进程是程序的执行过程,根据它的生命周期可以划分成 3 种状态。
执行态:
该进程正在运行,即进程正在占用 CPU, 任何时候都只有一个进程。
就绪态:
进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
等待态:
进程正在等待某些事件,当前不能分配时间片,进程不能使用 CPU,
若等待事件发生(等待的资源分配到)则可将其唤醒,变成就绪态。
3. 父子进程
3.1 PID和PPID
函数功能:
获取自己的进程PID
获取父进程的PPID
函数头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpid(void);
pid_t getppid(void);
函数参数:
无
函数返回值:
返回的就是你需要的PID号
代码:
void Test_Pid(void)
{
pid_t pd,ppd;
pd = getpid();
ppd = getppid();
printf("自己的进程号==%d\n",pd);
printf("父亲的进程号==%d\n",ppd);
}
结果:
3.2 fork
函数功能:在一个进程中创建一个子进程
函数头文件:<unistd.h>
函数原型:
pid_t fork(void)
函数参数:
无
函数返回值:
在本进程(父进程)
返回子进程的ID
在子进程
返回 0
返回负值
表示创建子进程失败
特点:
该函数会完全复制父进程的所有资源
但是会从fork之下开始运行
子进程所独有的只有它的进程号
代码:
//测试创建子进程函数fork()
void Test_fork(void)
{
int a=10;
char * str = "hello";
pid_t pd;
pid_t pd1,pd2;
printf("测试........\n");
pd = fork();//从这句话开始创建子进程
if(pd>0)//父进程
{
sleep(2);
pd1 = getpid();
pd2 = getppid();
printf("i am father\n");
printf("father 返回进程号==%d\n",pd);
printf("father 自己的进程号==%d\n",pd1);
printf("father 父亲的进程号==%d\n",pd2);
printf("a == %d\n",a);
}else //子进程
{
pd1 = getpid();
pd2 = getppid();
printf("i am son\n");
printf("son 返回进程号==%d\n",pd);
printf("son 自己的进程号==%d\n",pd1);
printf("son 父亲的进程号==%d\n",pd2);
a++;
}
}
结果:
3.3 vfork
函数功能:在一个进程中创建一个子进程
函数头文件:<unistd.h>
函数原型:
pid_t vfork(void)
函数参数:
无
函数返回值:
在本进程(父进程)
返回子进程的ID
在子进程
返回 0
vfork:
这个函数跟fork功能基本一致
只不过fork真正的创建了两个进程
而vfork虽然也是创建了两个进程
但是本质上来讲它只创建了一个
*会阻塞父进程
*必须等到子进程运行完毕(死掉)
*他才开始运行
*子进程跟父进程共占一个资源
*子进程结束exit函数之后
才会去运行父进程否则会发生段错误
fork:子进程拷贝父进程的数据段、堆栈段
vfork:父子进程共享数据段
代码:
//测试创建子进程函数vfork()
void Test_vfork(void)
{
int a=10;
char * str = "hello";
pid_t pd;
pid_t pd1,pd2;
printf("测试........\n");
pd = vfork();//从这句话开始创建子进程
if(pd>0)//父进程
{
pd1 = getpid();
pd2 = getppid();
printf(" i am father\n");
printf("father 返回进程号==%d\n",pd);
printf("father 自己的进程号==%d\n",pd1);
printf("father 父亲的进程号==%d\n",pd2);
printf("a == %d\n",a);
}else //子进程
{
pd1 = getpid();
pd2 = getppid();
printf(" i am son\n");
printf("son 返回进程号==%d\n",pd);
printf("son 自己的进程号==%d\n",pd1);
printf("son 父亲的进程号==%d\n",pd2);
a++;
exit(0);
}
}
结果:
如果exit(0)屏蔽掉:
4. 孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。
代码:
//测试孤儿进程
void Test_Guer(void)
{
int a=10;
char * str = "hello";
pid_t pd;
pid_t pd1,pd2;
printf("测试........\n");
pd = fork();//从这句话开始创建子进程
if(pd>0)//父进程
{
pd1 = getpid();
pd2 = getppid();
printf("i am father\n");
printf("father 返回进程号==%d\n",pd);
printf("father 自己的进程号==%d\n",pd1);
printf("father 父亲的进程号==%d\n",pd2);
printf("a == %d\n",a);
exit(0);
}else //子进程
{
sleep(2);
pd1 = getpid();
pd2 = getppid();
printf("i am son\n");
printf("son 返回进程号==%d\n",pd);
printf("son 自己的进程号==%d\n",pd1);
printf("son 父亲的进程号==%d\n",pd2);
a++;
}
}
结果:
5. 僵尸进程
子进程退出,父进程没有做清理工作。一种非常特殊的进程,它几乎已经 放弃了所有内存空间,没有任何可执行代码, 也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供 其他进程收集,除此之外,僵尸进程不再占有任何内存空间,父进程退出会清理子进程 。
这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
试想一下,如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。所以在实际编程中,避免和防范僵尸进程的产生显得尤为重要。
代码:
//测试僵尸进程
void Test_Z_jincheng(void)
{
int a=10;
char * str = "hello";
pid_t pd;
pid_t pd1,pd2;
printf("测试........\n");
pd = fork();//从这句话开始创建子进程
if(pd>0)//父进程
{
pd1 = getpid();
pd2 = getppid();
printf(" i am father\n");
printf("father 返回进程号==%d\n",pd);
printf("father 自己的进程号==%d\n",pd1);
printf("father 父亲的进程号==%d\n",pd2);
printf("a == %d\n",a);
while(1);
// exit(0);
}else //子进程
{
pd1 = getpid();
pd2 = getppid();
printf(" i am son\n");
printf("son 返回进程号==%d\n",pd);
printf("son 自己的进程号==%d\n",pd1);
printf("son 父亲的进程号==%d\n",pd2);
a++;
}
exit(0);
}
结果:
清除僵尸进程:
6.防止出现孤儿进程
进程的等待:
wait:等待子进程退出才会往下执行
整个程序会挂起
函数功能:等待任意的子进程结束
函数头文件:
<sys/types.h>
<sys/wait.h>
函数原型:
pid_t wait(int *status);
函数参数:
给NULL
函数返回值:
成功返回终止的那个子进程的pid
失败返回-1
函数功能:等待指定的子进程结束
函数头文件:
<sys/types.h>
<sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid,int * status,int options);
函数参数:
pid:你要等待的子进程的ID
status:NULL
函数返回值:
成功返回终止的那个子进程的pid
失败返回-1
代码:
//测试等待进程的函数(wait)
//防止出现孤儿进程
void test_wait(void)
{
pid_t pd1,pd2;
pid_t pd = fork();
pd1 = getpid();
pd2 = getppid();
if(pd > 0)//父进程
{
printf("hello i am father\n");
wait(NULL);//回收子进程
//防止子进程出现孤儿进程
printf("father 返回进程号==%d\n",pd);
printf("father 自己的进程号==%d\n",pd1);
printf("father 父亲的进程号==%d\n",pd2);
}else//子进程
{
sleep(1);
printf("i am son\n");
printf("son 返回进程号==%d\n",pd);
printf("son 自己的进程号==%d\n",pd1);
printf("son 父亲的进程号==%d\n",pd2);
}
}
结果:
7. 进程的死亡清理函数
会注册一个函数
当进程死掉的时候而且是正常死掉会去执行这个函数
函数功能:注册死亡函数
函数头文件:#include <stdlib.h>
函数的原型:int atexit(void (*function)(void));
函数的参数:
空参空返回值的函数指针
函数返回值:
0 成功
-1 失败
代码:
//测试死亡函数
void test_fcution(void)
{
printf("i am death function\n");
exit(0);
}
void Test_atexit(void)
{
char * str ="hello";
atexit(test_fcution);//注册死亡函数
printf("函数注册完毕\n");
exit(0);
}
结果:
8.继承函数
execl:
提供机制
当程序将要死亡的时候
调用该函数,会产生一个新的进程
来替代该程序的所有空间资源,包括进程号
提供了从一个进程启动另一个进程
函数API
函数功能:继承一个进程的空间
函数头文件: #include <unistd.h>
函数原型:
int execl(const char *path, const char *arg, ...);
函数参数:
path : 你要启动进程可执行的路径
const char *arg, ...:
函数的传参,NULL
返回值:成功函数没有返回,出错返回-1
代码:
//测试execl
void Test_execl(void)
{
pid_t pid =getpid();
pid_t ppid=getppid();
printf("正常执行打进程的pid==%d\n",pid);
printf("正常执行打进程的ppid==%d\n",ppid);
execlp("ls","NULL",(char *)NULL);
printf("hello world\n");
}
结果: