一.进程,多进程编程
1.1 什么是进程
平时写的代码,叫程序,是一个进程的概念我们使用./a.out 运行这个程序的时候,运行出来的东西,就叫做进程,他是一个动态的概念
动态体现在哪里:上下文切换,内存动态申请,动态释放
1.2 什么是多进程编程
理论上来说,在一个时间单位内,单核计算机只能运行一个进程。但是这个时间单位非常的短暂,非常快速的运行到下一个时间单位之后,就会切换到另一个进程运行,由于人的感官感觉不到这么短暂的时间,从而造成一种2个进程在同时运行的假象
而每个进程都有自己的内存空间和执行环境,彼此之间相互独立。这种编程模型可以允许程序同时执行多个任务,提高系统的并发性能和效率。在多进程编程中,通常使用系统调用(如fork())来创建新的进程,并通过进程间通信来实现进程之间的数据交换和协作。
二.进程的内存管理
1.1个进程总是占据4个gb的内存空间其中,前3个gb是该进程的独立内存空间,后1个gb是所有进程共享的内存空间。
但是注意:linux系统中,进程使用的其实不是物理内存,而是虚拟内存
4个gb全是虚拟内存,这4gb的虚拟内存,是由1gb的物理内存映射出来的
怎么做到1gb物理内存映射出4gb虚拟内存
a:平时我们写代码根本用不到1gb,1gb物理内存完全够用,
b:代码内存使用情况超过了1gb,物理内存就会向磁盘借用3gb,然后将活跃的数据交换到1gb物理内存中去使用,不活跃的数据,存放在3gb磁盘中。
三.进程的组成部分和进程编号
1.组成部分
1).pcb:包含了进程的各种属性和状态信息,如进程ID、优先级、程序计数器、寄存器状态等。
2).数据段:进程运行时使用的数据,包括全局变量、堆、栈等。
3).程序段:进程的执行指令集合,通常是可执行文件的指令序列。
2.进程编号
每一个进程都有自己独立的编号,称为pid
pid的分配方式为:循序寻找未使用的pid
0 1 2 3
此时打开一个新的进程
0 1 2 3 4
此时关闭2号进程
0 1 3 4
四.特殊进程(0,1,2)
0号进程 :又叫做 idel 或者 swapper进程:0号进程只负责1个事情,打开1号进程和2号进程,然后0号进程就进入休眠状态。如果计算机中没有任何进程运行的话,就会运行0号进程。
1号进程:又称为始祖进程 或者 init进程:1号进程开启之后,处于内核态,我们称为内核态1号进程,负责获取主机的所有硬件信息并初始化,初始化完成之后。初始化完所有硬件信息之后,内核态的1号进程,就会转变成用户态的1号进程,用户态的1号进程负责:开启所有的用户进程。
2号进程:kthread进程,他只有1个工作,用来对多个进程进行调度工作
0 1 2 这3个进程本质上是一个进程,3个分身(线程)
五.进程的分类
1) 交互进程:顾名思义,一个可以用户进程正常交互的进程(可以通过标准输入流,标准输出流,标准错误流与用户进行交互)。也就是说交互进程严重依赖终端
2) 批量处理进程:与交互进程是相对的,因为不需要实时响应用户的交互需求。批量处理进程就可以在某个时间点内,批量的运行一堆进程
我们能够运行的批量处理进程,是一个后台进程:在 ./a.out后面加上&,就可以让a.out进程在后台运行
注意:后台运行的进程,标准输出流和标准错误流还是借用运行他的终端,但是他不是一个交互进程
3)守护进程:是一个不与任何用户交互,不依赖任何进程组,独立存在的,在后台持续运行的一个服务性质的进程
那么如何成为一个守护进程
① 成为一个孤儿进程,脱离终端控制(这是初步脱离终端控制)
② 成为一个新的进程组的组长
pid_t setsid(void) 功能描述:如果调用者进程不是一个进程组的组长的话,则创建一个新的进程组,并且让该进程成为新的进程组的组长
③ 切换守护进程的工作目录,到系统根目录
int chdir(const char *path); 功能描述:将当前进程的工作目录切换到 path目录 返回值:成功返回0,失败返回-1
④ 设置守护进程的掩码为 0111
⑤ 彻底脱离与终端的联系:将守护进程的标准输入,输出,错误流全都重定向到独立的文件中去
六 进程相关的shell指令
ps -ef 显示进程之间关系的指令
ps -ajx 查看进程状态的指令
ps -aux 显示进程占据cpu资源情况
向进程发送信号的指令:kill
pidof 进程名 : 获取一个进程的pid
top :显示任务管理器:动态的显示系统中的所有进程
pstree 以树状图的形式,显示系统中所有进程关系
ps ef -o pid,ppid,command :只显示系统中所有进程的pid,ppid,和进程名
七.进程状态切换的模拟
1》运行一个死循环进程
2》键盘按 ctrl + z 中断进程的运行,这个进程就会在后台中断
此时该进程不仅中断了,还进入了后台,无法交互了
3》现在想要让他进入前台运行
在该进程之前运行的终端上,输入jobs,查看该进程的工作号
然后使用指令 fg 工作号,让该进程重新进入前台并运行
八.进程状态的切换
九.创建一个进程
1.如何打开一个新的进程实现并发:使用函数fork
pid_t fork(void);
功能描述:在当前进程中,创建一个新的进程。被创建的进程我们称为子进程,创建子进程的进程称为父进程 创建出来的子进程的特点: 1:创建出来的子进程,运行父进程fork之后的代码 2:子进程创建出来之后,会拷贝父进程的所有内存
2.如何区分fork之后,哪部分代码由父进程,哪部分代码由子进程执行
使用函数 getpid getppid 去实现
pid_t getpid(void);
功能描述:返回调用者进程的进程编号
pid_t getppid(void); 功能描述:返回调用者进程的父进程的进程编号 2:通过fork返回值去区分 其实,子进程子啊fork函数内部,就已经产生了 所以fork函数的前半段,只有父进程在运行,fork函数的后半段,由父子进程共同运行 注意,fork函数是有返回值的,返回值处于fork函数的后半段 所以fork函数返回值,由父子进程共同返回 父进程运行fork函数后半段部分:返回子进程的pid 子进程运行的fork函数后半段部分:返回0
十.孤儿进程,僵尸进程
一.孤儿进程
每一个进程占据4个gb的内存空间,当一个进程结束运行之后,理论上,要由他的父进程进行资源回收此时,如果说,一个进程还在运行,但是他的父进程已经死亡了。此时这个进程我们就称为孤儿进程在标准的linux系统里面,孤儿进程的资源,会由1号进程进行回收
注意:父进程回收死亡的子进程资源,也不是自动回收的,是要调用函数来回收的,以下2个函数可以回收子进程资源
pid_t wait(int *stat_loc);
功能描述:wait函数会获取等待回收资源的子进程的状态信息,并将获取到的状态信息保存到 stat_loc指向的内存空间中,然后回收子进程的资源. 如果不需要获取子进程死亡时候的信息的话,stat_loc参数直接传NULL或者0 问题在于:死亡的子进程状态信息怎么来? 进程main函数的返回值,或者exit函数的参数
注意:wait是一个阻塞型函数 void exit(int status); 功能描述:立刻技术当前进程,并且参数status作为该进程main函数的返回值进程返回 pid_t waitpid(pid_t pid, int *stat_loc, int options); 这个函数是wait函数的高阶自定义版本,允许自定义很多细节部分 参数pid: pid == -1: 可以回收任意子进程的资源 pid > 0:回收一个进程的资源,回收传入的pid进程的资源,但是要求该pid进程所在的进程组和父进程是一样的 pid == 0:回收任意进程组编号和父进程一样的子进程的资源 pid < -1:回收一个进程的资源,进程号为 |pid|那个进程的资源 参数options:一般有2个选项 0:waitpid函数为阻塞型函数 WNOHANG:waitpid函数为非阻塞型函数
二.僵尸进程
当子进程死亡,而父进程一直没有空去wait子进程的资源,此时该子进程资源就泄漏了,这样的子进程,我们称为僵尸进程
注意:僵尸进程是一个高危操作,尽量避免僵尸进程的出现
练习
今日作业:创建一对父子进程:
父进程负责向文件中写入 长方形的长和宽
子进程负责读取文件中的长宽信息后,计算长方形的面积(int)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <stdio.h>
// 今日作业:创建一对父子进程:
// 父进程负责向文件中写入 长方形的长和宽
// 子进程负责读取文件中的长宽信息后,计算长方形的面积 int
struct rectangle
{
int length;
int width;
} rec;
int main(int argc, const char *argv[])
{
int retval = fork();
if (retval > 0)
{
FILE *fd = fopen("test.txt", "w");
printf("请输入长方形的长");
scanf("%d", &rec.length);
printf("请输入长方形的宽");
scanf("%d", &rec.width);1
while (getchar() != '\n')
;
fwrite(&rec.length, 4, 1, fd);
fwrite(&rec.width, 4, 1, fd);
fclose(fd);
wait(0);
}
else if (retval == 0)
{
sleep(3);
FILE *fd = fopen("test.txt", "r");
fread(&rec.length, 4, 1, fd);
fread(&rec.width, 4, 1, fd);
int s = rec.length * rec.width;
printf("长方形的面积为;%d\n", s);
fclose(fd);
}
else
{
perror("fork");
}
return 0;
}
请输入长方形的长2
请输入长方形的宽3
长方形的面积为;6
[1] + Done "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-3gt1jwk4.knx" 1>"/tmp/Microsoft-MIEngine-Out-agsb05vn.az0"