Linux基础命令:
查看进程信息:
ps -aux | grep a.out a:所有用户进程 u:显示所有者、CPU、内存 x:无终端进程也显示查看线程信息:
ps -elf | grep a.out -e 列出所有进程 -l 长格式,显示更多详细字段 -f 完整格式,显示命令行、PPID父进程号什么是进程:
程序的一次运行实例称为进程,程序被加载进内存、操作系统为它分配资源后,正在运行的实体
进程是操作系统资源分配的最小单位。资源包括:内存空间、文件描述符、信号处理表、工作目录、用户权限等。
在系统中使用什么结构体表示一个进程:
Linux 内核中,每一个进程在内核里都对应一个struct task_struct结构体,这个结构体就是大名鼎鼎的PCB :Process Control Block 进程控制块。所有进程的task_struct被内核放进一个双向循环链表,叫进程链表,内核遍历这条链表就能查到系统所有进程,就是ps命令的数据源。
系统通过什么来区分两个正在运行的进程:
Process ID,进程号,唯一区分每一个运行中的 task_struct进程。
PPID,父进程 ID,只是记录它的父进程是谁,多个子进程可以有同一个 PPID,不唯一,不能区分进程。
当一个进程运行后,32bit的系统给用户分配的内存空间多大,有哪些区域,每个区域存储什么
32 位地址总线,最大寻址空间:2的32次方=4GB
- 用户空间:0 ~ 3GB(0x00000000 ~ 0xBFFFFFFF) 大小 = 3GB
- 内核空间:3GB ~ 4GB(0xC0000000 ~ 0xFFFFFFFF) 大小 = 1GB
用户空间 3GB 内部分区,从低地址→高地址
地址从小到大顺序:代码段 → 数据段 → BSS 段 → 堆 → 栈 → 共享库区
1. 代码段 .text
存放编译好的机器指令、程序二进制代码,属性为只读,可执行,防止运行时修改指令,声明周期为,整个进程运行期间
2. 初始化数据段 .data数据段
存放已经初始化的全局变量、static 静态变量,属性可读、可写入
3. 未初始化数据段 .bss
存放未初始化的全局变量、未初始化静态变量,程序加载时内核会自动把这片内存清零。
.text + .data + .bss 这三块的大小在编译时就已经确定,运行中不会扩张收缩。
4. 堆 heap
属性:读写,运行时可以动态扩大、缩小。手动释放 free ();忘记释放 → 内存泄漏。堆区位于 BSS 上方,地址向上增长,从低地址往高地址扩张。
申请堆区空间的函数
malloc() calloc() realloc()5. 共享库 / 内存映射区 mmap
堆和栈中间的一片区域。存放:动态库 .so 文件(shturl.)、mmap 映射的文件、匿名共享内存、线程栈也常分配在这里
6. 用户栈 stack(栈)
位置靠近 3GB 边界的高地址,向下增长从高地址→低地址,默认大小一般 8MB。存放局部变量、函数参数、函数返回地址、ebp栈帧、寄存器现场、函数调用的时候栈自动伸缩,函数返回自动释放。递归太深,栈不断向下扩张,超出 8MB →栈溢出 Segmentation fault
0x00000000 ┌─────────────────────┐ │ .text 代码段 │ ├─────────────────────┤ │ .data 初始化数据 │ ├─────────────────────┤ │ .bss 未初始化数据 │ ├─────────────────────┤ │ heap 堆 ↑向上扩 │ │ │ │ 空闲间隙 │ │ mmap共享库区 │ │ │ │ stack 栈 ↓向下扩 │ ├─────────────────────┤ │ 命令行参数、环境变量 │ 0xBFFFFFFF (3G边界) | ─────────────────────── 0xC0000000 ┌─────────────────────┐ │ 内核空间 1GB │ │ (所有进程共享) │ └─────────────────────┘ 0xFFFFFFFF (4G)每个进程 3GB 用户空间,开 10 个进程是不是就要 30GB 物理内存?
不是!虚拟内存≠物理内存,内核通过页表,把虚拟地址映射到物理内存;多个进程虚拟地址可以映射到不同物理页,也可以共享物理页(共享库)。
什么是虚拟地址
虚拟地址是操作系统为每个应用进程独立分配的虚构内存地址,并非内存条上真实的物理地址。所有程序代码、指针操作、变量地址,全程使用的都是虚拟地址,CPU 不会直接通过虚拟地址读写真实内存。
硬件 MMU + 系统页表会自动完成虚拟地址→物理地址的映射转换: 进程只认虚拟地址,无需感知真实内存位置;无映射的虚拟地址访问会触发缺页异常,由系统动态分配内存。
核心作用
- 内存隔离安全:每个进程虚拟地址空间独立,相同虚拟地址互不干扰,杜绝进程互相篡改内存、程序崩溃连锁影响。
- 内存高效利用:支持虚拟内存、内存置换,闲置数据可暂存硬盘,突破物理内存大小限制运行程序。
- 程序通用可加载:程序基于虚拟地址编译,无需绑定固定物理内存位置,可灵活加载到内存任意位置。
- 权限管控:可对虚拟地址设置读、写、执行权限,保护内核数据和程序代码安全。
多进程可以干什么
1. 实现并发任务,几件事一起跑;实现多任务,并发任务。
2. 故障隔离,一个任务崩了不连累主程序;每个进程内存独立、互相隔离。
3. 充分利用多核 CPU 算力;启多个进程,操作系统可以把不同进程调度到不同 CPU 核心,实现真正的并行计算
4. 解耦业务,程序模块化拆分;模块之间使用IPC通信,单个模块可以单独重启、升级;
进程的状态有哪些
1. 新建(创建态)
进程刚被创建,PCB 进程控制块正在初始化,资源还没分配完成,还没进入就绪队列。
2. 就绪态 Ready
- 已经分配好了除CPU以外的所有资源
- 万事俱备,只等 CPU 调度,一旦获得 CPU 立刻运行
3. 运行态 Running
- 进程占有 CPU,正在处理器上执行代码
- 单核 CPU 同一时刻只能有1 个进程处于运行态
4. 阻塞态(等待态 / 睡眠态)Blocked
进程等待某事件,主动放弃 CPU。 例:等待键盘输入、读磁盘数据、等待信号、等待锁。⚠️阻塞不能直接回到运行态!事件完成后,只能先回到就绪态。
5. 终止态(结束态)Terminated
进程执行完毕,或被杀死。PCB 资源等待操作系统回收。
子进程创建后拥有哪些资源
子进程几乎复制父进程的地址空间,采用写时复制 COW,并不是立刻全部拷贝。
进程地址空间(虚拟内存)
- 代码段 (.text)、只读数据段 (.rodata)
- 全局数据段 (.data/.bss)、堆
- 用户栈
- 初始父子指向同一块物理内存;一旦任意一方修改数据,操作系统才复制一份(写时复制)。
文件描述符表父进程所有打开过的 fd (0 标准输入、1 标准输出、2 标准错误、文件、socket) 都会复制一份。
- 父子共享同一个文件表项(文件偏移量、文件状态标志)
- 一个进程修改文件读写偏移,另一个也会受影响。
用户 ID、组 ID,会话 ID、进程组 ID
- uid、gid、有效用户 ID
- 会话 sid、进程组 pgid
- 控制终端
信号处理方式
- 每个信号的处理函数 (SIG_DFL / SIG_IGN / 自定义函数) 被复制。
- 未处理的信号不会继承
当前工作目录、根目录、文件权限掩码 umask
环境变量表、资源限制 rlimit
子进程不会继承的资源
- 进程 ID (PID):自己全新的 pid,父进程 pid (PPID) 是父进程 id
- 父进程的锁(互斥锁、文件锁)不会继承
- 闹钟定时器 alarm,未到期的闹钟被清除
- 父进程挂起的信号队列,不会传给子进程
- 每个进程独立的内核栈、PCB 进程控制块
进程退出后发生了什么,如何避免僵尸进程
- 进程调用
exit()/return/ 被信号杀死 → 进入终止状态 - 用户空间资源(堆、栈、文件描述符)立刻释放
- 但是 PCB(进程控制块)不会马上释放!
- 内核保留 PCB,存放进程退出状态码
- 等待父进程调用 wait () /waitpid ()读取退出状态
- 如果父进程一直不调用 wait 回收
- 子进程就变成僵尸进程 (Zombie,状态 Z)
避免僵尸进程:
方案 1:父进程主动调用 wait () /waitpid ()(阻塞回收)
方案 2:父进程非阻塞轮询 waitpid
方案 3:信号处理,等待SIGCHLD信号到来,在信号回调函数回收子进程资源
方案 4:使用孙进程,或退出父进程,使得子进程可以被init收养,会自动回收资源
在子进程中需要执行一个外部可执行程序,需要怎么做,与原来子进程相比发生了什么变化
子进程调用 exec 系列函数,加载外部可执行程序
常用函数:execl、execlp、execv、execvp、execve
exec 执行之后,子进程发生了哪些变化
✅ 会被替换、销毁掉的资源
- 用户空间地址空间完全清空
- 原来 fork 复制过来的 .text 代码段、.data、.bss、堆、用户栈,全部丢弃
- 载入新程序的代码、数据,开辟全新堆栈
- 原来子进程里面定义的变量、函数全部消失,再也不能访问
- exec 成功,旧程序被完全覆盖。
✅保留不变,继承下来的内核资源——不会被替换
- PID 进程 ID 不变!!(最重要!PID 永远不会变)
- 文件描述符 fd(默认继承,打开的文件、socket 还在)
- 可以用
fcntl(fd, F_SETFD, FD_CLOEXEC)设置执行 exec 时自动关闭该文件描述符 - 进程组 ID、会话 ID、控制终端
- 用户 ID、组 ID(uid,gid)
- 当前工作目录、根目录、umask 掩码
- 信号处理方式:
- 原先自定义的信号处理函数,全部重置为默认 SIG_DFL
- 被忽略的信号 (SIG_IGN),保持忽略状态不变
✅ 新生成、全新的资源
- 新程序的代码段、数据段、堆、栈
- 全新的信号栈
- 新程序 main 函数的 argv 参数、环境变量(execve 可以自定义环境)
什么是线程
线程Thread:操作系统能够进行 CPU 调度和分派的最小单位。
- 进程:资源分配的最小单位,拥有独立地址空间、文件、堆内存
- 线程:CPU 调度执行的最小单位,不拥有独立资源,共用所属进程的资源
| 对比项 | 进程 | 线程 |
|---|---|---|
| 资源所有权 | 拥有独立内存、地址空间 | 共享所属进程资源 |
| 切换开销 | 大(进程上下文切换) | 小(线程上下文切换) |
| 隔离性 | 进程之间完全独立,互不影响 | 一条线程崩溃,整个进程直接崩溃 |
| 通信 | 进程间通信 IPC(管道、共享内存、消息队列) | 直接读写全局变量,通信简单 |
单线程:任务排队,做完一个再做下一个。
多线程:一个程序里同时开启多条执行流,宏观上并发执行多个任务。
注意:单核 CPU 同一时刻只能跑 1 个线程,靠 CPU 快速轮流切换线程,制造 “同时运行” 的假象;多核 CPU 才可以做到真正的并行。
创建线程函数,函数指针的作用
创建线程函数原型如下
int pthread_create( pthread_t *thread, //线程的tid号 const pthread_attr_t *attr, //线程属性,默认NULL为不分离,需要主动回收 void *(*start_routine) (void *), //重点:线程函数指针 void *arg //传给线程执行函数的参数 );start_routine保存的是线程入口函数的地址- 操作系统新建出来的子线程,启动之后,就跳转到这个地址开始执行代码
- 主线程执行
main(); 新建的子线程没有自带入口,必须你告诉它:去跑哪一个函数。这个 “告诉” 就是靠函数指针传过去。 - 函数指针就是指向执行函数首地址的一个指针,在这里直接填函数名。是因为函数名出现在这个创建线程的函数中的时候,编译器会自动将函数名转化为函数指针
- 实际上函数名只是一个代码字段的入口地址标签,类似于汇编语言里loop标签,用于跳转作用,并不等于是一个指针,指针是一种变量类型,因此一个函数指针是可以操作指向任意同类型的函数的,而这里的函数名,只能代表自己这个函数。
线程分离属性 detachstate
1. 基础概念
每个pthread线程都有一个属性:分离属性 (detachstate),只有二种状态:
① 可接合态(默认)PTHREAD_CREATE_JOINABLE;线程创建出来默认就是可接合
- 线程退出后,它的退出状态、返回值会被保留在内存中(TCB 资源不会释放)
- 必须由别的线程调用:
pthread_join(tid, &retval)join会:等待线程结束 + 回收线程资源 + 获取返回值
- 后果:忘记 join → 线程变成僵尸线程,资源泄漏
② 分离态PTHREAD_CREATE_DETACHED
- 线程退出时,自动释放所有线程资源,没人可以再 pthread_join 它
- 不能调用
pthread_join(),调用会直接失败 - 线程的返回值会直接丢弃,无法获取
- 一句话总结:可接合:资源等别人来回收;分离:线程干完活自己回收自己。
什么是同步和互斥,Linux提供的对应机制名称是什么,什么是内核对象
互斥 (Mutex‑Mutual Exclusion)
目的:解决资源竞争问题,保护临界资源多个线程不能同时访问同一个共享资源,同一时刻只允许一个线程进入临界区。
目的:排他、独占、抢资源、防乱改
例子:两个线程同时修改同一个全局变量,加互斥锁保证一次只有一个线程修改。
2. 同步 (Synchronization)
目的:控制线程执行先后顺序,线程之间按约定次序运行线程 A 必须等线程 B 做完某件事之后,A 才能继续执行。
目的:先后顺序、等待、通知、条件依赖
例子:生产者先放数据,消费者才能取数据;消费者没数据就要阻塞等待生产者。
内核对象是由 Linux 操作系统内核管理、创建、维护的数据结构,存放在内核空间,用户程序不能直接访问它。用户进程只能拿到一个句柄 / 文件描述符 ID,通过系统调用,让内核替你操作这个对象。
用户对象 VS 内核对象
- 用户对象:在你的程序 (用户空间) 内存里,由应用代码管理。 例如:
pthread_mutex_t、普通局部变量、全局变量。 - 注意:pthread_mutex 互斥锁本质是用户态对象,部分锁操作可以不陷入内核。
- 内核对象:在内核空间,归操作系统管理。进程消亡内核对象不一定立刻销毁。
Linux 常见内核对象举例
- 进程 PCB (task_struct)
- 文件、文件描述符 fd
- 管道
- System V 信号量、消息队列、共享内存段
- 信号、定时器
- 套接字 socket
什么是死锁,死锁的四个必要条件,如何避免死锁,发生死锁如何解决
死锁:两个或多个线程 / 进程,各自拿着对方需要的资源,又互相等待对方释放资源,所有人都被永久阻塞,谁也无法继续往下执行。
- 互斥条件资源同一时刻只能被一个线程占用;别人想要必须等待。锁就是典型互斥资源。
请求与保持条件(占有且等待)线程已经占有了至少一个资源,又去请求另一个被别人占有的资源,并且不释放自己手上已经拿到的锁。
不可剥夺条件别人已经拿到的资源,不能被其他线程强行抢走;只能由持有者主动释放。 互斥锁不能被别的线程强制解锁。
环路等待条件线程之间形成一条等待闭环:A 等 B、B 等 C、C 等 A,构成一个资源等待的环形链。
⚠️注意:四个条件只是必要条件,不是充分条件。四个条件同时具备不一定死锁,但是死锁发生的时候,四个条件一定全部成立。需要解决死锁,破坏死锁的四个必要条件即可
1. 破坏「互斥条件」
尽量不用互斥;使用无锁方案。大部分临界资源必须互斥访问,这条很难破坏,很少用。
2. 破坏「请求与保持」
方案:线程一次性申请所有需要的锁,要么全部拿到,一把都不拿; 不允许拿一把锁之后,再去申请第二把。
3. 破坏「不可剥夺」
如果申请不到新锁,主动放弃自己手里已经持有的所有锁,过一会再重新申请。
4. 破坏「环路等待」⭐最常用、最简单
给所有锁规定统一的申请顺序,所有线程必须按相同顺序拿锁。
进程间通信方式
1. 管道 Pipe(匿名管道)
- 特点:半双工(单向通信);只能用于有血缘关系进程(父子、兄弟进程);数据一次性读取,读完就没;存在内核缓冲区。
- 使用:
pipe()系统调用 - 限制:没有名字,只能通过继承文件描述符传递。
2. 命名管道 FIFO(有名管道)
- 特点:半双工;无血缘进程之间也可以通信;在磁盘上生成一个管道文件,文件仅起标识,数据仍然存放内核缓冲区。
- 使用:
mkfifo()创建 - 管道 (Pipe)、FIFO 本质都是字节流,没有消息边界。
3. 消息队列 Message Queue(System‑V IPC)
- 特点:内核创建的队列;进程间传递带类型的消息块;数据有边界;消息读取可以按消息类型读取,不一定要先进先出;消息读完不会自动消失。
- 内核对象,生命周期随内核;进程退出队列不会销毁。
- API:
msgget、msgsnd、msgrcv、msgctl
4. 共享内存 Shared Memory
- 速度最快的 IPC 方式!多个进程同一块物理内存映射到自己的虚拟地址空间。
- 缺点:本身不带同步互斥机制,必须额外搭配信号量、互斥锁来保护数据。
- API:
shmget、shmat、shmdt、shmctl
5. 信号量 Semaphore
信号量不是用来传递数据!!它是同步互斥工具,用来解决进程 / 线程资源竞争。
- System‑V 信号量:一组信号量集合;常用于配合共享内存做进程同步。
- POSIX 有名 / 无名信号量:线程、进程都能用。
- API:
semget、semop、semctl
6. 信号 Signal
- Linux 异步通信方式。进程收到信号后中断当前工作,执行信号处理函数。
- 属于简单通知机制,只能传递一个信号编号,不能携带大量数据。
- 例子:
SIGKILL、SIGINT(ctrl+c) - API:
kill()、raise()、signal()、sigaction()
7. 套接字 Socket(网络 IPC)
- 支持本机进程通信 + 跨主机网络通信。
- 本地域套接字 (Unix‑domain socket):仅本机进程通信,不走网卡,效率高。
- TCP/UDP socket:跨电脑通信。
| 通信方式 | 能否传大量数据 | 有无同步能力 | 适用进程 | 特点 |
|---|---|---|---|---|
| 匿名管道 pipe | 中等 | 自带读写阻塞 | 父子进程 | 单向,血缘进程 |
| 有名管道 fifo | 中等 | 自带读写阻塞 | 任意进程 | 磁盘管道文件 |
| 消息队列 | 较大 | 自带阻塞 | 任意进程 | 带消息类型,面向消息 |
| 共享内存 | 最大 | 无同步,需额外加锁 | 任意进程 | 速度最快 |
| 信号量 | 不能传数据 | 专门做同步互斥 | - | 锁工具 |
| 信号 signal | 极少 (仅编号) | 异步通知 | 任意进程 | 简单事件通知 |
| socket | 大 | 协议控制 | 本机 / 跨主机 | 网络通信 |
- 匿名管道最大限制:没有名字,无法被外部非亲缘进程打开,只能 fork 继承 fd。
- FIFO 就是为了解决匿名管道不能跨非亲缘进程而生。
- System‑V 的三种 IPC(消息队列、共享内存、信号量)依靠
key值标识,完全不需要血缘。 - 信号只要拿到 PID,不管有没有血缘,都可以发送。
有名管道的读阻塞、写阻塞和管道破裂的条件
| 操作 | 条件 | 行为 |
|---|---|---|
| open (O_RDONLY, 阻塞) | 无写端打开 | 读端阻塞 |
| open (O_WRONLY, 阻塞) | 无读端打开 | 写端阻塞 |
| read() | 缓冲区空,写端存活 | 读阻塞 |
| read() | 缓冲区空,所有写端关闭 | read 返回 0 |
| write() | 管道缓冲区满,读端存活 | 写阻塞 |
| write() | 所有读端已经关闭,执行 write | 发送 SIGPIPE,管道破裂 |
无名管道 (pipe) 和 有名管道 (FIFO)
1. 无名管道 pipe
int pipe(int fd[2]);- 没有文件名、没有磁盘节点,只存在于内存内核缓冲区
- 只能用于具有血缘关系的进程通信:父子进程、兄弟进程(fork 出来)
- 半双工通信:一端读、一端写;两个文件描述符:
fd[0]读端,fd[1]写端 - 生命周期随进程:所有相关进程关闭管道,管道自动销毁
- 只能单向数据流;若要双向通信,需要创建 2 条管道
- 不能通过 open 打开,只能由 pipe 创建,再 fork 共享文件描述符
2. 有名管道 FIFO
int mkfifo(const char *pathname, mode_t mode);- 有文件名,存在于文件系统,是一个特殊管道文件(磁盘上仅占 inode,数据还是放内核内存缓冲区)
- 无血缘限制:任意两个进程,只要知道管道文件名就可以通信
- 半双工,同样单向;双向通信也要两条 FIFO
- 生命周期持久:创建之后文件一直存在,调用
unlink()才删除;进程退出管道文件不会自动消失 - 使用方式:
mkfifo创建 →open打开 → read/write 读写 → close 关闭 → unlink 删除
| 对比项 | 无名管道 pipe | 有名管道 FIFO |
|---|---|---|
| 存储位置 | 内核缓冲区 (内存),无文件 | 文件系统有文件名,数据仍在内核缓冲区 |
| 通信进程 | 必须有血缘关系 (父子 / 兄弟) | 任意进程,无血缘要求 |
| 创建函数 | pipe() | mkfifo() |
| 打开方式 | fork 继承文件描述符,不能 open | 使用open()打开管道文件 |
| 生命周期 | 所有进程关闭后,管道消失 | 文件永久存在,需要 unlink 删除 |
| 访问路径 | 无路径名 | 有路径名 |
| 阻塞特性 | 读写都会阻塞 (默认) | 读写都会阻塞 (默认) |
| 适用场景 | 父子进程快速 IPC | 互不相关进程之间通信 |
Linux中什么是信号,最常用的信号有哪些
信号Signal是 Linux/Unix 下一种异步通信方式,属于进程间通信 (IPC)。
- 信号本质就是一个数字编号,用来通知进程发生了某个事件。
- 信号是异步:进程不知道信号什么时候到来,信号可以在进程运行的任意时刻打断它。
- 谁可以发信号:内核、另一个进程、键盘快捷键(Ctrl+C)。
- 进程收到信号后,有 3 种处理方式:
- 默认动作:系统预设行为(终止、暂停、忽略、核心转储)
- 忽略信号:收到信号什么也不干
signal() / sigaction() - 捕获信号(自定义处理):注册一个信号处理函数,收到信号就执行自己写的回调函数
- ⚠️ 有两个信号不能被捕获、不能被忽略:
SIGKILL(9)、SIGSTOP(19)
| 信号名 | 编号 | 含义 | 触发来源 | 默认动作 |
|---|---|---|---|---|
SIGINT | 2 | 中断信号 | 键盘Ctrl + C | 终止进程 |
SIGQUIT | 3 | 退出信号 | 键盘Ctrl + \ | 终止 + 生成 core 核心转储文件 |
SIGKILL | 9 | 强制杀死进程 | kill -9 进程号 | 立刻终止,不可捕获 / 忽略 |
SIGSEGV | 11 | 段错误(非法内存访问) | 代码越界、空指针解引用 | 终止 + core 转储 |
SIGALRM | 14 | 闹钟信号 | alarm()定时函数 | 终止进程 |
SIGTERM | 15 | 优雅终止信号 | kill 进程号(不带‑9) | 终止进程,可以被捕获忽略 |
SIGSTOP | 19 | 暂停进程 | Ctrl+Z、kill‑19 | 暂停,不可捕获 / 忽略 |
SIGCONT | 18 | 继续运行暂停进程 | kill -18 | 恢复被暂停的进程运行 |
SIGCHLD | 17 | 子进程状态改变(子进程退出) | 子进程结束,发给父进程 | 默认忽略 |
收到信号之后可以有哪些处理方式,可以修改处理动作吗
默认处理(SIG_DFL)
Linux 内核预先规定好该信号的默认行为,一共就 5 类:
- 终止进程:退出程序,如 SIGINT (2)、SIGTERM (15)
- 终止 + 生成 core 转储文件:方便调试崩溃,如 SIGQUIT (3)、SIGSEGV (11)
- 暂停进程:进程挂起停止运行,如 SIGSTOP (19)
- 继续运行:唤醒暂停进程,如 SIGCONT (18)
- 忽略信号:收到信号什么都不做,如 SIGCHLD (17) 默认忽略
2. 忽略信号(SIG_IGN)
进程收到信号直接丢掉,完全不响应。
例外:SIGKILL (9)、SIGSTOP (19) 绝对不能忽略
3. 捕获信号(自定义信号处理函数)
程序员写一个回调函数,当信号到来时,内核打断当前代码,跳转去执行你写的信号处理函数,处理完再回到原来代码继续运行。
例外:SIGKILL、SIGSTOP 不能捕获
修改处理动作:注册对应信号的处理函数
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);共享内存是什么,共享内存和管道的区别
共享内存(Shared Memory)是Linux 进程间通信 IPC 的一种方式。通过在内核开辟一块物理内存缓冲区,映射到多个不同进程的虚拟地址空间。 多个进程都可以直接读写这同一块内存,进程之间不需要内核转发拷贝数据。
| 对比项 | 管道 (pipe/FIFO) | 共享内存 (System‑V) |
|---|---|---|
| 数据传递方式 | 两次拷贝 (用户→内核→用户) | 无拷贝,直接访问物理内存 |
| 速度 | 慢 | 最快 IPC |
| 同步互斥 | 自带同步阻塞机制,内核管理 | 无同步!需要程序员手动加锁 / 信号量 |
| 数据特性 | 字节流;读走之后数据就消失 | 数据保留,可反复读写 |
| 通信方向 | 半双工 (单向) | 全双工,双向读写 |
| 进程关系 | pipe:要有血缘;FIFO 无血缘 | 任意进程,无血缘限制 |
| 生命周期 | 管道:所有进程关闭就销毁FIFO:文件需 unlink 删除 | 共享内存段手动 shmctl 删除,进程退出不会自动删 |
| 适用场景 | 少量数据、简单顺序传输 | 大批量数据高速交换 |
- 管道通信时数据需要从发送进程拷贝到内核缓冲区,再从内核拷贝到接收进程;共享内存将同一块物理内存映射到多个进程虚拟地址,进程直接读写内存,省去两次拷贝,效率更高。
- 管道自带内核提供的同步阻塞机制;共享内存没有同步机制,必须额外使用信号量等方式实现进程同步,防止竞争访问。
- 管道的数据一旦被读取就消失;共享内存的数据可以被多个进程反复读取。
Linux进程调度算法
- 实时进程(Real‑time):优先级高,对响应时间要求苛刻。
- 普通进程(CFS,完全公平调度):交互式程序、后台任务。
1、实时进程调度算法(2 种)
实时进程优先级:1‑99,数字越大优先级越高。
(1)SCHED_FIFO 先来先服务(FIFO)
- 非时间片轮转
- 一旦进程获得 CPU,就一直运行,直到:主动放弃 CPU、阻塞、被更高优先级进程抢占
- 同优先级:先就绪先运行
- 低优先级进程永远抢不过高优先级
(2)SCHED_RR 时间片轮转
- 带时间片的 FIFO
- 同优先级进程轮流执行,分配固定时间片
- 时间片用完,放到同优先级队列末尾;高优先级依然可以随时抢占
- 实时进程可以抢占普通进程
2、普通进程调度:CFS 完全公平调度器(Linux 2.6.23 之后默认)
调度策略:SCHED_OTHER
CFS 核心思想
不按固定时间片,给每个进程分配公平的 CPU 运行时间。引入关键概念:
- 虚拟运行时间 vruntime
- 进程每跑一会,虚拟时间就累加
- 优先级高(nice 值小)→ vruntime 增长慢,能获得更多 CPU 时间
- 红黑树(平衡二叉搜索树)
- 就绪进程全部放在红黑树里
- 每次选 vruntime 最小的进程上 CPU 运行
- nice 值:
‑20 ~ 19,默认 0- nice 越小 → 优先级越高,分得 CPU 越多
锁的机制
| 锁类型 | 核心特点 | 典型场景 |
|---|---|---|
| 互斥锁 Mutex | 同一时间仅 1 个线程访问;谁上锁谁解锁 | 普通共享资源、队列保护 |
| 递归锁 | 同一个线程可重复上锁 | 递归函数 |
| 读写锁 rwlock | 读共享,写独占 | 读多写少数据 |
| 自旋锁 | 抢不到锁就空转,不休眠 | 锁占用时间极短,内核 |
| 信号量 Semaphore | 计数,可跨线程释放锁 | 资源限流、进程间同步 |