一、进程介绍
1.进程的含义
进程是一个程序执行的过程,会去分配内存资源,cpu的调度
2.进程控制块pcb
pcb 是一个结构体(task_struct),process control block 进程控制块
task_struct包括以下成员(不完整)
PID 进程标识符
当前工作路径
umask(0002)
进程会记录打开的文件,形成文件列表
signal信号的相关设置,处理异步io(信号如crtl + c,结束a.out运行)
用户id,组id
进程资源的上限
ulimit -a,显示资源上限
2.进程和程序的区别
程序:静态
存储在硬盘中代码,数据的集合
进程:动态
程序执行的过程,包括进程的创建、调度、消亡
.c ----> a.out-----> process(pid)
1)程序是永存,进程是暂时的
2)进程有程序状态的变化,程序没有
3)进程可以并发,程序无并发
4)进程与进程之间会存在竞争计算机的资源
5)一个程序可以运行多次,变成多个进程
一个进程可以运行一个或多个程序
4.进程的内存分布
0-3G是进程的空间,3G-4G是内核的空间,虚拟地址
进程空间分以下区

栈存储:局部变量、函数参数、返回地址
5.进程分类
(1)交互式进程:需要与用户进行交互,对响应时间要求较高。如命令行界面程序
(2)批处理进程:无需与用户交互,按照预定的顺序依次执行。如后台批处理任务、shell脚本
(3) 守护进程:是在后台运行且不受终端控制的进程,特定条件触发才会运行,大部分时间处于休眠状态。如杀毒软件
6.进程的状态
基本操作系统:3个状态,就绪→执行态→阻塞(等待,睡眠)

linux中的状态,运行态,睡眠态,僵尸,暂停态。

linux中stopped是暂停,terminal才是完全终止
7.进程的调度
进程上下文切换
内核主要功能之一就是完成进程调度, 硬件,bios,io,文件系统,驱动
进程可以认为是:宏观并发,微观串行
并发是指一个时间段内可以认为是同时进行的,但从微观上看还是先运行一个再执行另一个的。
并行是指一个时间点是同时运行的,是真正的同时。
二、查询进程相关命令
1.ps aux
查看进程相关信息
R:就绪态、运行态
睡眠态、等待态
S:可唤醒等待态
D:不可唤醒等待态
T:停止态
Z:僵尸态
结束态
2.top
根据CPU占用率查看进程相关信息
3.kill和killall
发送一个信号
信号从1到64
-1 或 -HUP :挂起信号,通常用于通知进程重新读取配置文件。
-2 或 -INT :中断信号,类似于在终端按下 Ctrl + C 。
-9 或 -KILL :强制终止信号,进程无法捕获和处理。
-15 或 -TERM :默认的终止信号,进程可以自行处理终止操作。
kill -2 29637(PID)
发送信号+PID对应的进程,默认接收者关闭
killall -9 进程名
发送信号+进程名(对应的所有进程)
eg:killall a.out
三、进程相关函数
1.fork
pid_t fork();
一次调用,会返回两次。
子进程先运行或是父进程先运行,顺序不确定。
变量不共享。
子进程复制父进程的0到3g空间和父进程内核中的PCB,但pid号不同。
功能:通过该函数可以从当前进程中克隆一个同名新进程。
克隆的进程称为子进程,原有的进程称为 父进程。
子进程是父进程的完全拷贝。
子进程的执行过程是从fork函数之后执行。
子进程与父进程具有相同的代码逻辑。
返回值:int 类型的数字。
在父进程中:成功 返回值是子进程的pid号 >0
失败 返回-1;
在子进程中:成功 返回值 0
失败 无
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
//father
while(1)
{
printf("send vedio\n");
sleep(1);
}
}
else if(ret==0)
{
//child
while(1)
{
printf("control move\n");
sleep(1);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
2.getpid
pid_t getpid(void);
功能:
获得调用该函数进程的pid(当前进程的pid)
参数:无
返回值:
进程的pid
3.getppid
pid_t getppid(void);
功能:
获得调用该函数进程的父进程pid号
参数:无
返回值:
返回父进程id号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int a=20;
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
//father
sleep(2);
printf("father a is %d pid:%d ppid:%d\n",a
,getpid(),getppid());
}
else if(ret==0)
{
//child
printf("child a is %d pid:%d ppid:%d\n",a
,getpid(),getppid());
a +=10;
printf("child a is %d\n",a);
}
else
{
perror("fork");
return 1;
}
printf("a is %d pid:%d\n",a,getpid());
return 0;
}
fork()&&fork()||fork()
几个进程? 五个 (看最后一层有多少个进程)
分析:fork()它返回值有两种情况,大于0,等于0,&&如果左边为1,右边执行,如果为0,右边不执行,|| 如果左边为1,右边不执行,如果左边为0,右边执行,按照这个思想去分析。
四、父子进程的关系
子进程是父进程的副本。子进程获得父进程数据段,堆,栈,正文段共享。
在fork之后,一般情况父还是子会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。
父与子的区别:
1)fork的返回值
2)pid不同
1.继承和传递
1)资源继承:子进程继承父进程的许多属性,如文件描述符、环境变量、工作目录等,但它们的进程 ID 是唯一的。
2)信号处理:父进程可以设置信号处理程序来响应SIGCHLD信号,从而对子进程的状态变化作出反应。例如,当子进程退出时,父进程可以执行清理工作。
2.进程的终止
8种情况
1)main 中return
2)exit(), c库函数,会立即结束程序的执行,会执行io库的清理工作,关闭所有的流,以及所有打开的文件,并确保缓冲区中的数据被刷新到文件。注册清理函数(atexit),使用 atexit() 函数可以注册多个清理函数,这些函数会在 exit() 被调用时执行。
3)_exit,_exit 会关闭所有的已经打开的文件,不执行清理函数。
4)主线程退出
5)主线程调用pthread_exit 异常终止
6)abort()
7)signal:发送信号 kill pid
8)最后一个线程被pthread_cancle
3.进程的退出
僵尸进程和孤儿进程
1)僵尸进程(Zombie Process)(会残留pcb块,大量占用系统内核)
定义:
- 僵尸进程是指一个已经终止执行但其进程表项仍然保留在系统中的进程,也就是进程执行结束但空间未被回收。这是因为其父进程尚未读取该进程的退出状态。(子先消亡)
产生原因:
- 当一个子进程终止时,它会发送一个 SIGCHLD 信号给其父进程,父进程通过调用 wait() 或 waitpid() 来读取子进程的退出状态。
- 如果父进程没有调用 wait() 或 waitpid() 来处理子进程的退出状态,子进程的进程表项将会被保留在系统中,状态为“僵尸”,那么子进程将会变成僵尸进程。
特征:僵尸进程的状态通常在 ps 命令中显示为 Z 或 z(表示 Zombie)。
处理方式:
- 通常,父进程应当调用 wait() 或 waitpid() 来收集子进程的退出状态,避免僵尸进程的产生。
如果父进程无法处理僵尸进程,系统会将其交给 init 进程(PID 1)处理,init 进程会调用 wait() 以回收所有僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t ret = fork();
if(ret>0)
{
//father
printf("father pid %d ,ppid:%d \n",getpid(),getppid());
sleep(10);//延迟父进程,使子进程先结束,但是由于子进程没释放完,子进程会变成僵尸态
}
else if(0 == ret)
{
//child
printf("child pid:%d ppid:%d\n",getpid(),getppid());
exit(0);
}
else
{
perror("fork error\n");
return 1;
}
return 0;
}
2)孤儿进程(Orphan Process)
定义:
- 孤儿进程是指其父进程已经终止,而它还在继续运行的进程。此时,这些孤儿进程会被系统的 init 进程(PID 1)收养。
产生原因:
- 当一个父进程在其子进程还在运行时终止,这些子进程变成孤儿进程。init 进程会接管这些孤儿进程,并成为它们的新父进程。
特征:
- 孤儿进程的状态是正常的,只是它们的父进程已经结束。它们仍然在运行,可能继续执行它们的任务。
- 孤儿进程会被 init 进程收养,init 进程会负责回收这些进程的资源并处理其退出状态。
处理方式:
- 孤儿进程通常不需要特别处理,因为 init 进程会自动处理它们的生命周期。
- 不同于僵尸进程,孤儿进程不会无限期占用进程表项。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t ret = fork();
if(ret>0)
{
//father
printf("father pid %d ,ppid:%d \n",getpid(),getppid());
exit(0);
}
else if(0 == ret)
{
//child
printf("child pid:%d ppid:%d\n",getpid(),getppid());
sleep(3);//延迟子进程,父进程先结束,变成孤儿进程
printf("child pid:%d ppid:%d\n",getpid(),getppid());
}
else
{
perror("fork error\n");
return 1;
}
return 0;
}
五、退出函数
1.exit 库函数
退出状态,终止的进程会通知父进程,自己是如何终止的。如果是正常结束(终止),则由exit传入的参数。如果是异常终止,则有内核通知异常终止原因的状态。任何情况下,父进程都能使用wait,waitpid获得这个状态,以及资源的回收。
void exit(int status)
exit(1);
功能:
让进程退出,并刷新缓存区
参数:
status:进程退出的状态
返回值:
无
EXIT_SUCCESS 0
EXIT_FAILURE 1
return 当该关键字出现在main函数中时候可以结束进程
如果在其他函数中则表示结束该函数。
exit -> 刷新缓存区 -> atexit注册的清理函数 -> _exit
1)参数
- status: 进程的退出状态码。通常情况下,返回值 0 表示正常退出,而非零值表示异常或错误退出。具体的非零值可以用来表示不同的错误类型,具体取决于程序的约定或错误处理策略。
- 正常退出:如果 status 为 0,表示程序正常退出。
- 异常退出:如果 status 为非零值,表示程序异常退出或出现了错误。
2)功能
- 清理操作:调用 exit 时,程序会进行一系列清理操作,包括关闭所有打开的文件描述符、清理分配的内存等。
- 执行终止处理函数:exit 会调用所有通过 atexit 注册的终止处理函数(clean-up functions)。
- 返回退出状态:status 参数值会作为进程的退出状态返回给操作系统,父进程可以通过 wait 或 waitpid 来获取这个值。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE* fp = fopen("1.txt","w");
char buf[512]="hello,123";
fputs(buf,fp);
exit(0);
printf("aaaaaaaaaaa\n");//用来检测是否已经退出的,如果退出成功,就不会打印
return 0;
}
2._exit 系统调用
void _exit(int status);
功能:
让进程退出,不刷新缓存区
参数:
status:进程退出状态
返回值:
无
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
FILE* fp = fopen("1.txt","w");
char buf[512]="hello,123";
fputs(buf,fp);
_exit(0);// 不会刷新缓冲区
printf("aaaaaaaaaaa\n");
return 0;
}
3.atexit
int atexit(void (*function)(void)); 回调函数
功能:
注册进程退出前执行的函数
参数:
function:函数指针
指向void返回值void参数的函数指针
返回值:
成功返回0
失败返回非0
当程序调用exit或者由main函数执行return时,所有用atexit注册的退出函数,将会由注册时顺序倒序被调用(注册的清理函数按后进先出(LIFO)的顺序调用)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char * p;//由于函数无法传参,定义为全局变量,使clean函数可以使用
void clean(void)
{
printf("clean func,p %s\n",p);
free(p);
}
int main(int argc, char *argv[])
{
atexit(clean);//回调函数
p = (char*)malloc(50);
strcpy(p,"hello");
printf("main p %s\n",p);
exit(0);
return 0;
}

1560

被折叠的 条评论
为什么被折叠?



