Linux多进程开发
2024-06-18 14:46:50 4 举报
AI智能生成
Linux后端
作者其他创作
大纲/内容
进程概述
程序和进程
程序
程序是包含一系列信息的文件
这些信息描述了如何在运行时创建一个进程
二进制格式标识
每个程序文件都包含用于描述可执行文件格式的元信息
内核利用此信息来解释文件中的其他信息(ELF可执行链接格式)
机器语言指令
对程序算法进行编码
程序入口地址
标识程序开始执行时的起始指令位置
数据
程序文件包含的变量初始值和程序使用的字面量值(比如字符串)
符号表及重定位表
描述程序中函数和变量的位置及名称
这些表格有多重用途,其中包括调试和运行时的符号解析(动态链接)
共享库和动态链接信息
程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态连接器的路径名
其他信息
程序文件还包含许多其他信息,用以描述如何创建进程
进程
简介
进程是正在运行的程序的实例
是一个具有一定独立功能的程序关于某个数据集合的一次运行活动
操作系统动态执行的基本单元
联系
可以用一个程序来创建多个进程
进程是由内核定义的抽象实体,并为该实体分配用以执行程序的各项系统资源
进程由用户内存空间和一系列内核数据结构组成
其中用户内存空间包含了程序代码及代码所使用的变量
内核数据结构则用于维护进程状态信息
与进程相关的标识号(IDs)
虚拟内存表
文件的描述符表
信号传递及处理的有关信息
进程资源使用及限制
当前工作目录
其他信息
单道、多道程序设计
单道程序
在计算机内存中只允许一个的程序运行
多道程序设计
在计算机内存中同时存放几道相互独立的程序
为了提高 CPU 的利用率
任意时刻,一个CPU 上运行的程序只有一个
当下常见 CPU 为纳秒级,1秒可以执行大约 10 亿条指令,由于人眼的反应速度是毫秒级,所以看似同时在运行
时间片
又称为“量子(quantum)”或“处理器片(processor slice)”是操作系统分配给每个正在运行的进程微观上的一段 CPU 时间
通常很短(在 Linux 上为 5ms-800ms)
时间片由操作系统内核的调度程序分配给每个进程
首先,内核会给每个进程分配相等的初始时间片
然后每个进程轮番地执行相应的时间
当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片
并行和并发
并行(parallel)
指在同一时刻,有多条指令在多个处理器上同时执行
并发(concurrency)
指在同一时刻只能有一条指令执行
但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果
进程控制块(PCB)
为了管理进程,内核必须对每个进程所做的事情进行清楚的描述
内核为每个进程分配一个 PCB(Processing Control Block)进程控制块,维护进程相关的信息
Linux 内核的进程控制块是 task_struct 结构体
在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查看 struct task_struct 结构体定义
主要字段(红色表示fork完全复制,蓝色表示fork特殊处理,紫色表示fork不会复制)
基础字段
pid进程id
系统中每个进程有唯一的 id,用 pid_t 类型表示,其实就是一个非负整数
tgid线程组的id
用于多线程
parent
指向创建该进程的父进程的task_struct
children
一个列表,包含这个进程创建的所有子进程
时间和计时
utime用户态消耗的CPU时间
stime内核态消耗的CPU时间
start_time: 进程开始的时间。
文件系统
fs: 指向文件系统信息(如root, pwd等)的结构体。
包括当前工作目录、根目录等
文件描述符表
files:指向 files_struct 结构的指针
该结构描述了进程打开的所有文件和相关的文件描述符
文件描述符表的file_struct会复制,但是文件描述符通常是一个索引,指向一个全局的文件对象表,这个表是共享的,但描述符的引用计数会增加。这意味着父子进程会共享相同的文件偏移量
内存管理
mm: 指向mm_struct的指针,用于管理进程地址空间的信息。
包含了虚拟地址空间的所有信息,例如页表、代码段、数据段和堆栈段的地址范围
子进程获得父进程地址空间的一个副本,但通常这是通过“写时复制”(Copy-on-Write, COW)机制实现的
进程可以使用的资源上限
rlim:这是一个 resource_limits 结构
描述了进程可以使用的各种资源(如CPU时间、内存大小等)的上限
子进程"继承"父进程的rlim,但是它可以独立动态地去修改
和信号相关的信息
signal:这个字段指向 signal_struct,其中包括与控制终端相关的信息,如前台进程组ID。
blocked:当前被阻塞的信号集
pending:当前待处理的信号集
状态和调度
state进程的状态
有就绪、运行(RUNNING)、挂起、停止(STOPPED)等状态
prio: 进程的动态优先级
由内核决定
用于短期精细的调度
static_prio: 进程的静态优先级
由用户或进程自身决定
用于长期大致的优先级
normal_prio: 基于进程类型和优先级计算出的标准优先级
进程可以使用的资源上限
进程切换时需要保存和恢复的一些CPU寄存器
thread_struct:保存特定于架构的寄存器信息,例如程序计数器、堆栈指针等。
umask掩码
umask:保存文件创建模式掩码
用户ID和组ID
cred:这是一个指向 cred 结构的指针
该结构包含了用户ID(uid)、组ID(gid)以及其他权限和安全相关信息
会话和进程组
session:会话ID
pgrp:进程组ID
其他
flags: 各种进程标志。
comm: 存储进程名。
进程状态转换
状态模型
三态模型
就绪态
进程具备运行条件,等待系统分配处理器以便运
行。
当进程已分配到除CPU以外的所有必要资源后,只要再
获得CPU,便可立即执行
获得CPU,便可立即执行
在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列
运行态
进程占有处理器正在运行
阻塞态
又称为等待(wait)态或睡眠(sleep)态
指进程不具备运行条件,正在等待某个事件的完成
五态模型
新建态
进程刚被创建时的状态,尚未进入就绪队列
终止态
进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终止权的进程所终止时所处的状态
进入终止态的进程以后不再执行,但依然保留在操作系统中等待善后
一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程
进程相关命令
查看进程
ps aux / ajx
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息
STAT参数意义
D 不可中断 Uninterruptible(usually IO)
R 正在运行,或在队列中的进程
S(大写) 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组
实时显示进程动态
top
可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔
在 top 命令执行后,可以按以下按键对显示的结果进行排序
M 根据内存使用量排序
P 根据 CPU 占有率排序
T 根据进程运行时间长短排序
U 根据用户名来筛选进程
K 输入指定的 PID 杀死进程
杀死进程
kill [-signal] pid
kill –l 列出所有信号
kill –SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程
进程号和相关函数
每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767
进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。
任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。
进程组是一个或多个进程的集合他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)
默认情况下,当前的进程号会当做当前的进程组号
进程号和进程组相关函数
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);
进程相关函数
进程创建fork
进程创建
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成
进程树结构模型。
进程树结构模型。
函数原型
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值
成功:子进程中返回 0,父进程中返回子进程 ID
失败:返回 -1
失败的主要原因
当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置
为 EAGAIN
为 EAGAIN
系统内存不足,这时 errno 的值被设置为 ENOMEM
父子进程虚拟地址空间
fork之后,子进程的用户区和父进程一样
内核区的大部分也会拷贝过来
pid不会拷贝
GDB多进程调试
可以在 fork 函数调用之前,通过指令设置 GDB 调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程
设置调试父进程或者子进程:set follow-fork-mode [parent(默认)| child]
设置调试模式:set detach-on-fork [on | off]
默认为 on,表示调试当前进程的时候,其它的进程继续运行,如果为 off,调试当前进
程的时候,其它进程被 GDB 挂起。
程的时候,其它进程被 GDB 挂起。
查看调试的进程:info inferiors
切换当前调试的进程:inferior id
使进程脱离 GDB 调试:detach inferiors id
进程退出
void _exit(int status); 或 void _Exit(int status);
属于Linux系统函数
功能
立即终止调用进程,并返回一个状态码给父进程。它不执行任何额外的清理操作。
参数
status 是返回给父进程的退出状态
void exit(int status);
C语言函数
功能
执行一些额外清理操作,然后再调用_exit函数
额外操作
调用所有已注册的 atexit 和 on_exit 函数
刷新所有标准 I/O 流
关闭所有标准 I/O 流
删除临时文件
进程回收
简介
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内
存等
存等
但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息
(包括进程号、退出状态、运行时间等)
(包括进程号、退出状态、运行时间等)
父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程
wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。
注意
一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
种类
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能
等待任意一个子进程结束,如果任意一个子进程结束了,次函数会回收子进程的资源。
参数
进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
例如我使用kill -9杀死子进程,9会被传入wstatus中
返回值
成功
返回被回收的子进程的id
失败
-1(所有子进程都结束,调用函数失败)
注意
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能
回收指定进程号的子进程,可以设置是否阻塞
参数
pid
pid > 0 : 某个子进程的pid
pid = 0 : 回收当前进程组的所有子进程
pid = -1 : 回收所有的子进程,相当于 wait() (最常用)
pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程
options
设置阻塞或者非阻塞
0 : 阻塞
WNOHANG : 非阻塞
返回值
> 0 : 返回子进程的id
=0:在非阻塞情况下会出现,表示还有子进程未退出
-1:错误,没有子进程了
exec函数族
作用
是根据指定的文件名找到可执行文件,并用它来取代调用进程的
内容
内容
换句话说,就是在调用进程内部执行一个可执行文件
一般创建一个子进程执行exec函数族
特点
exec 函数族的函数执行成功后不会返回
调用进程的实体(用户区),包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样
只有调用失败了,它们才会返回 -1,设置errno,从原程序的调用点接着往下执行
种类
函数族后缀含义
l(list)
参数地址列表,以空指针结尾
v(vector)
存有各参数地址的指针数组的地址
p(path)
按 PATH 环境变量指定的目录搜索可执行文件
e(environment)
存有环境变量字符串地址的指针数组的地址
函数种类
int execl(const char *path, const char *arg, .../* (char *) NULL */);
第一个参数是要执行程序的路径
后面的参数是传递给程序的参数列表,以 NULL 结尾
第一个参数一般是程序的名称
从第二个开始是程序执行需要的参数
有时将NULL成为哨兵
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
第一个参数是要执行程序的文件名,它会去环境变量中查找指定文件
后面的参数是传递给程序的参数列表,以 NULL 结尾
int execle(const char *path, const char *arg, .../*, (char *) NULL, char *
const envp[] */);
const envp[] */);
最后一个参数是一个环境变量数组
int execv(const char *path, char *const argv[]);
第二个参数是一个字符串数组,其中包含要传递给新程序的所有参数
int execvp(const char *file, char *const argv[]);
第一个参数是要执行程序的文件名
第二个参数是一个字符串数组,其中包含要传递给新程序的所有参数
int execvpe(const char *file, char *const argv[], char *const envp[]);
非POSIX标准,某些Unix-like系统可用(如Linux)
int execve(const char *filename, char *const argv[], char *const envp[]);
exec 函数族中最底层的系统函数,属于Linux系统函数,其他函数最终都会调用这个函数
第一个参数是要执行的程序文件名
第二个参数是一个字符串数组(参数列表)
第三个参数是一个环境变量数组
特殊进程
孤儿进程
父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程
(Orphan Process)。
(Orphan Process)。
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init
进程会循环地 wait() 它的已经退出的子进程
进程会循环地 wait() 它的已经退出的子进程
孤儿进程并不会有什么危害
僵尸进程
每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法
自己释放掉,需要父进程去释放。
自己释放掉,需要父进程去释放。
进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸
(Zombie)进程
(Zombie)进程
僵尸进程不能被 kill -9 杀死,如果父进程不wait(),进程号就会被一直占用,而进程号有限,僵尸进程过多会导致不能产生新的进程
终端
在 UNIX 系统中,用户通过终端登录系统后得到一个 shell 进程,这个终端成为shell 进程的控制终端(Controlling Terminal)
进程中,控制终端是保存在 PCB 中的信息,而 fork() 会复制 PCB 中的信息,因此由 shell 进程启动的其它进程的控制终端也是这个终端
默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指
向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准
错误输出写也就是输出到显示器上
向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准
错误输出写也就是输出到显示器上
在控制终端输入一些特殊的控制键可以给前台进程发信号,例如 Ctrl + C 会产
生 SIGINT 信号,Ctrl + \ 会产生 SIGQUIT 信号
生 SIGINT 信号,Ctrl + \ 会产生 SIGQUIT 信号
进程组和会话
介绍
进程组和会话在进程之间形成了一种两级层次关系
进程组是一组相关进程的集合
会话是一组相关进程组的集合
进程组和会话是为支持 shell 作业控制而定义的抽象概念,用户通过 shell 能够交互式地在前台或后台运行命令
进行组由一个或多个共享同一进程组标识符(PGID)的进程组成。一个进程组拥有一
个进程组首进程,该进程是创建该组的进程,其进程 ID 为该进程组的 ID,新进程
会继承其父进程所属的进程组 ID
个进程组首进程,该进程是创建该组的进程,其进程 ID 为该进程组的 ID,新进程
会继承其父进程所属的进程组 ID
进程组拥有一个生命周期,其开始时间为首进程创建组的时刻,结束时间为最后一个
成员进程退出组的时刻。一个进程可能会因为终止而退出进程组,也可能会因为加入
了另外一个进程组而退出进程组。进程组首进程无需是最后一个离开进程组的成员
成员进程退出组的时刻。一个进程可能会因为终止而退出进程组,也可能会因为加入
了另外一个进程组而退出进程组。进程组首进程无需是最后一个离开进程组的成员
会话首进程是创建该新会话的进程,其进程 ID 会成为会
话 ID。新进程会继承其父进程的会话 ID
话 ID。新进程会继承其父进程的会话 ID
一个会话中的所有进程共享单个控制终端。控制终端会在会话首进程首次打开一个终
端设备时被建立。一个终端最多可能会成为一个会话的控制终端
端设备时被建立。一个终端最多可能会成为一个会话的控制终端
在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为
后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终
端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员
后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终
端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员
当控制终端的连接建立起来之后,会话首进程会成为该终端的控制进程
相关函数
getpgrp
作用:获取调用进程的进程组ID
语法:pid_t getpgrp(void);
返回值:返回调用进程的进程组ID。
getpgid
作用:获取指定进程的进程组ID
语法:pid_t getpgid(pid_t pid);
参数
pid:要查询的进程ID。如果pid为0,则返回调用进程的进程组ID
返回值
返回指定进程的进程组ID
失败时返回-1
setpgid
作用:设置进程的进程组ID
语法:int setpgid(pid_t pid, pid_t pgid);
参数
pid:要设置的进程ID。如果pid为0,则使用调用进程的ID。
pgid:要设置的进程组ID。如果pgid为0,则使用pid指定的进程ID作为新的进程组ID
返回值
成功时返回0
失败时返回-1
getsid
作用:获取指定进程的会话ID
语法:pid_t getsid(pid_t pid);
参数
pid:要查询的进程ID。如果pid为0,则返回调用进程的会话ID
返回值
返回指定进程的会话ID
失败时返回-1
setsid
作用:创建一个新会话,并使调用进程成为该新会话的领导进程
语法:pid_t setsid(void);
返回值
成功时返回新会话的ID
失败时返回-1
守护进程
简介
也就是通常说的 Daemon 进程(精灵进程)
是Linux 中的后台服务进程
它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以 d 结尾的名字
特征
生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭
它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号(如 SIGINT、SIGQUIT)
示例
Linux 的大多数服务器就是用守护进程实现的
比如,Internet 服务器 inetd,Web服务器 httpd 等
创建步骤
执行一个 fork(),之后父进程退出,子进程继续执行
子进程调用 setsid() 开启一个新会话
清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限
修改进程的当前工作目录,通常会改为根目录(/)
关闭守护进程从其父进程继承而来的所有打开着的文件描述符
在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2()
使所有这些描述符指向这个设备
核心业务逻辑
进程间的通信
场景
进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间
的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源
的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源
但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程
间通信( IPC:Inter Processes Communication )
间通信( IPC:Inter Processes Communication )
目的
数据传输
一个进程需要将它的数据发送给另一个进程
通知事件
一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
资源共享
多个进程之间共享同样的资源
需要内核提供互斥和同步机制
进程控制
有些进程希望完全控制另一个进程的执行(如 Debug 进程)
此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
方式概览
统一主机进程间间通信
UNIX进程间通信方式
匿名管道
有名管道、
信号
System V进程间通信方式
POSIX进程间通信方式
不同主机(网络)进程间通信
Socket
管道
无名管道
UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制
例如
统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两
个进程来分别执行 ls 和 wc
个进程来分别执行 ls 和 wc
特点
匿名管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同。
管道拥有文件的特质:读操作、写操作,匿名管道没有文件实体
一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念
从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少
通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的
在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的
从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据
匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用
通信原理
父子、兄弟之间的文件描述符表是共享的
父进程打开管道的情况下,子进程也会打开相同的管道
缓冲区数据结构
一般是循环队列
用法
创建匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
int pipe(int pipefd[2]);
参数
int pipefd[2] 这个数组是一个传出参数
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的写端
返回值
0成功
-1失败
管道是默认阻塞的,如果没有数据,read阻塞,如果管道满了,write阻塞
查看缓冲区大小命令
ulimit -a
查看缓冲区大小函数
#include <unistd.h>
long fpathconf(int fd, int name);
long fpathconf(int fd, int name);
有名管道(FIFO)
用于处理非亲缘关系进程间的通信
特点
提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中
打开方式与打开一个普通文件是一样的
只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据
一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用了(如read()、write()和close())
与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的
和匿名管道区别
FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中
当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用
FIFO 有名字,不相关的进程可以通过打开有名管道进行通信
使用
通过命令创建有名管道
mkfifo 名字
通过函数创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数
pathname: 管道名称的路径
mode: 文件的权限 和 open 的 mode 是一样的是一个八进制的数
返回值
成功返回0,失败返回-1,并设置错误号
注意事项
在管道启动之前
一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
一个为只写而打开一个管道的进程会阻塞(此时不会写任何数据),直到另外一个进程为只读打开管道
在管道启动之后
读管道
管道中有数据
read返回实际读到的字节数
管道中无数据
管道写端被全部关闭
read返回0,(相当于读到文件末尾)
写端没有全部被关闭,read阻塞等待
写管道
管道读端被全部关闭
管道破裂,进程异常终止(收到一个SIGPIPE信号)
管道读端没有全部关闭
管道已经满了
write会阻塞
管道没有满
write将数据写入,并返回实际写入的字节数
内存映射
将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件
当两个进程映射同一个磁盘文件到内存的时候,就能实现两个进程的通信
函数原型
创建映射
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
参数
void *addr: 映射的内存的地址,用户传递NULL, 由内核指定
length
要映射的数据的长度
这个值不能为0。建议使用文件的长度
获取文件的长度:stat lseek
如果大小不满足分页,系统会自动取分页的整数倍
prot对申请的内存映射区的操作权限
PROT_EXEC
可执行的权限
PROT_READ
读权限
PROT_WRITE
写权限
PROT_NONE
没有权限
要操作映射内存,必须要有读的权限
flags
MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项
MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
fd需要映射的那个文件的文件描述符
通过open得到,open的是一个磁盘文件
注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突
prot: PROT_READ open:只读/读写
prot: PROT_READ | PROT_WRITE open:读写
offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移
返回值
成功返回创建的内存的首地址
失败返回MAP_FAILED,(void *) -1
释放映射
int munmap(void *addr, size_t length);
功能:释放内存映射
参数
addr : 要释放的内存的首地址,mmap会获得
length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
进程通信
有关系的进程(父子进程
还没有子进程的时候通过唯一的父进程,先创建内存映射区
有了内存映射区以后,创建子进程
父子进程共享创建的内存映射区
没有关系的进程间通信
准备一个大小不是0的磁盘文件
进程1 通过磁盘文件创建内存映射区得到一个操作这块内存的指针
进程2 通过磁盘文件创建内存映射区得到一个操作这块内存的指针
使用内存映射区通信
内存映射区通信,是非阻塞的
信号
信号
简介
事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟
是一种异步通信的方式
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件
产生信号的事件
对于前台进程用户可以通过输入特殊的终端字符来给它发送信号
硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给
相关进程
相关进程
诸如被 0 除,或者引用了无法访问的内存区域
系统状态变化
比如 alarm 定时器到期将引起 SIGALRM 信号
进程执行的 CPU 时间超限,或者该进程的某个子进程退出
运行 kill 命令或调用 kill 函数
概念
目的
让进程知道已经发生了一个特定的事情
强迫进程执行它自己代码中的信号处理程序
特点
简单
不能携带大量信息
满足某个特定条件才发送
优先级比较高
信号列表
查看方式
kill -l
前31个信号为常规信号,其余为实时信号
重要信号
2.SIGINT
事件
当用户按下了<Ctrl+C>组合键时,用户终端向正
在运行中的由该终端启动的程序发出此信号
在运行中的由该终端启动的程序发出此信号
默认动作
终止进程
3.SIGQUIT
事件
用户按下<Ctrl+\>组合键时产生该信号,用户终
端向正在运行中的由该终端启动的程序发出些信号
端向正在运行中的由该终端启动的程序发出些信号
默认动作
终止进程
9.SIGKILL
事件
无条件终止进程。该信号不能被忽略,处理和阻塞
默认动作
终止进程,可以杀死任何进程
11.SIGSEGV
事件
指示进程进行了无效内存访问(段错误)
默认动作
终止进程并产生core文件
13.SIGPIPE
事件
Broken pipe向一个没有读端的管道写数据
默认动作
终止进程
17.SIGCHLD
事件
子进程结束时,父进程会收到这个信号
产生条件
子进程终止时
子进程接收到 SIGSTOP 信号停止时
子进程处在停止态,接受到SIGCONT后唤醒时
默认动作
父进程会忽略这个信号
18.SIGCONT
事件
如果进程已停止,则使其继续运行
默认动作
继续/忽略
19.SIGSTOP
事件
停止进程的执行。信号不能被忽略,处理和阻塞
默认动作
为终止进程
处理动作
查看详细信息
man 7 signal
5种默认处理动作
Term 终止进程
Ign 当前进程忽略掉这个信号
Core 终止进程,并生成一个Core文件
Stop 暂停当前进程
Cont 继续执行当前被暂停的进程
信号的状态
产生
未决
递达
SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作
相关函数
kill函数
作用
向进程发送信号
函数原型
int kill(pid_t pid, int sig);
参数
pid 进程ID
sig 要发送的信号
返回值
成功返回0
失败返回-1
raise函数
作用
用于向当前进程发送信号
函数原型
int raise(int sig);
参数
sig:要发送的信号
返回值
成功返回0
失败返回非0值
abort函数
作用
用于异常终止当前进程。
函数原型
void abort(void);
参数
无
返回值
无(进程会被终止)
alarm函数
作用
设置一个定时器,当定时器到达时,会向进程发送一个SIGALRM信号
函数原型
unsigned int alarm(unsigned int seconds);
参数
seconds
定时器的时间,单位是秒
返回值
返回之前设置的定时器的剩余时间
setitimer函数
作用
用于设置或取消间隔定时器
函数原型
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数
which
定时器类型
new_value
新的定时器设置
old_value
旧的定时器设置
返回值
成功0
失败-1
信号捕捉函数
signal函数
作用
用于设置一个信号的处理函数(信号处理器)
函数原型
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sighandler_t signal(int signum, sighandler_t handler);
参数
signum:要捕获的信号编号
handler:当信号到达时要调用的函数。也可以是特殊值 SIG_IGN(忽略信号)或 SIG_DFL(采用默认行为)
返回值
成功时返回之前的信号处理函数
失败时返回 SIG_ERR
sigaction函数
作用
用于详细地设置信号的行为,比 signal 函数提供更多的控制
函数原型
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数
signum
要操作的信号
act
新的信号处理设置(一个 sigaction 结构体)
oldact
用于保存旧的信号处理设置
返回值
成功返回0
失败返回-1
struct sigaction各个字段
sa_handler
类型:void (*sa_handler)(int)
描述:这是一个指向信号处理函数(信号处理器)的指针。当指定的信号到达时,这个函数会被调用
参数:该函数接受一个整数参数,表示接收到的信号的编号。
sa_sigaction
类型:void (*sa_sigaction)(int, siginfo_t *, void *)
描述:这是一个指向另一种类型的信号处理函数的指针,与 sa_handler 不同,这个函数可以接收更多的信息关于触发信号的事件
参数
第一个参数是信号编号
第二个参数是一个指向 siginfo_t 结构体的指针,该结构体包含有关信号的额外信息
第三个参数通常用于传递上下文信息(通常是一个指向 ucontext_t 结构体的指针)
sa_mask
类型:sigset_t
描述:这是一个信号集,用于指定哪些信号应该在信号处理函数执行期间被阻塞(即暂时不递送
sa_flags
类型:int
描述这是一个标志集,用于改变信号处理的其他各种行为
常见标志
SA_NOCLDSTOP:使父进程在其子进程暂停或继续时不会收到 SIGCHLD 信号
SA_NOCLDWAIT:使父进程在其子进程终止时不会变成僵尸进程
SA_NODEFER:允许在信号处理函数运行时重新触发该信号
SA_ONSTACK:为该信号处理程序使用一个单独的信号栈
SA_RESETHAND:在信号处理函数被调用后重置信号的处理方式为默认行为。
SA_SIGINFO:指示使用 sa_sigaction 字段而不是 sa_handler。
sa_restorer
类型:void (*sa_restorer)(void)
描述:这个字段是废弃的,不应在新的程序中使用。
信号集
简介
许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为
信号集的数据结构来表示,其系统数据类型为 sigset_t
信号集的数据结构来表示,其系统数据类型为 sigset_t
两个信号集在PCB中使用位图机制实现,但操作系统不允许我
们直接对这两个信号集进行位操作,而需自定义另外一个集合,借助信号集操作函数
来对 PCB 中的这两个信号集进行修改
们直接对这两个信号集进行位操作,而需自定义另外一个集合,借助信号集操作函数
来对 PCB 中的这两个信号集进行修改
种类
阻塞信号集
信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生
信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,
所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作
所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作
未决信号集
信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间
相关函数
sigemptyset
作用:初始化一个空的信号集
语法:int sigemptyset(sigset_t *set);
参数
set:指向信号集的指针
返回值:成功返回0,失败返回-1
sigfillset
作用:初始化一个包含所有可捕获信号的信号集
语法:int sigfillset(sigset_t *set);
参数
set:指向信号集的指针
返回值:成功返回0,失败返回-1
sigaddset
作用:向信号集中添加一个信号。
语法:int sigaddset(sigset_t *set, int signum)
参数
set:指向信号集的指针。
signum:要添加的信号编号。
返回值:成功返回0,失败返回-1。
sigdelset
作用:从信号集中删除一个信号
语法:int sigdelset(sigset_t *set, int signum);
参数
set:指向信号集的指针
signum:要删除的信号编号。
返回值:成功返回0,失败返回-1。
sigismember
作用:检查一个信号是否在信号集中
语法:int sigismember(const sigset_t *set, int signum);
参数
set:指向信号集的指针
signum:要检查的信号编号。
返回值:如果信号在集合中,返回1;如果不在,返回0;如果出错,返回-1
sigprocmask
作用:用于检查或修改进程的信号掩码
语法:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
how:操作类型,通常是SIG_BLOCK(添加信号到掩码)、SIG_UNBLOCK(从掩码中删除信号)或SIG_SETMASK(设置新的信号掩码)。
set:新的信号掩码。
oldset:用于保存旧的信号掩码。
sigpending
作用:获取当前进程的待处理信号集
语法:int sigpending(sigset_t *set);
参数:
set:用于存储待处理信号集的指针。
返回值:成功返回0,失败返回-1。
内核实现信号捕捉的过程
共享内存
简介
共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)
由于一个共享内存段会称为一个进程用户空间的一部分,因此这种 IPC 机制无需内核介入
所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用
与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快
使用步骤
调用 shmget() 创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段)这个调用将返回后续调用中需要用到的共享内存标识符
使用 shmat() 来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。
此刻在程序中可以像对待其他可用内存那样对待这个共享内存段
为引用这块共享内存,程序需要使用由 shmat() 调用返回的 addr 值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针
调用 shmdt() 来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。
调用 shmctl() 来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步
相关函数
shmget
作用:获取一个共享内存标识符(ID)
语法:int shmget(key_t key, size_t size, int shmflg);
参数
key:共享内存的键
size:共享内存段的大小
shmflg:操作标志,通常包括权限设置。
返回值
成功返回共享内存ID
失败返回-1
shmat
作用:将共享内存段附加到进程的地址空间。
语法:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid:共享内存ID
shmaddr:附加的地址(通常设置为NULL,让系统选择)。
shmflg:操作标志
返回值
成功返回附加内存的地址
失败返回(void *) -1
shmdt
作用:断开共享内存的附加
语法:int shmdt(const void *shmaddr);
参数
shmaddr:先前通过shmat附加的内存地址
返回值
成功返回0
失败返回-1
shmctl
作用:对共享内存进行控制操作
语法:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:共享内存ID
cmd:控制命令,如IPC_STAT(获取状态)、IPC_SET(设置参数)或IPC_RMID(删除
buf:指向shmid_ds结构体的指针,用于存储共享内存的信息。
返回值
成功返回0
失败返回-1
ftok
作用:生成一个唯一的键(key)
语法:key_t ftok(const char *pathname, int proj_id);
参数
pathname:文件的路径名
proj_id:项目ID(通常是一个字符)
返回值
成功返回一个非零键
失败返回-1
相关操作
ipcs用法
-a
打印当前系统中所有的进程间通信方式的信息
-m
打印出使用共享内存进行进程间通信的信息
-q
打印出使用消息队列进行进程间通信的信息
-s
打印出使用信号进行进程间通信的信息
ipcrm用法
-M shmkey
移除用shmkey创建的共享内存段
-m shmid
移除用shmid标识的共享内存段
-Q msgkey
移除用msqkey创建的消息队列
-q msqid
移除用msqid标识的消息队列
-S semkey
移除用semkey创建的信号
-s semid
移除用semid标识的信号
0 条评论
下一页