提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
1,进程创建、回收(以linux为例)
在linux系统中,“进程”是程序的运行实例。我们以fork()和exec()为切入点:
1.1 进程创建流程
阶段 | 内容说明 |
---|---|
fork() |
创建一个新的子进程,子进程复制父进程的地址空间,但逻辑上是两个独立进程。 |
exec() |
子进程可以通过 exec 系列函数加载一个新的程序映像,替换自己的代码段、数据段等。 |
clone() |
更底层的创建方式(底层系统调用),fork() 等也基于 clone() 实现。 |
示例:
#include <unistd.h>
#include <sys/types.h>
#included <stdio.h>
int main()
{
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程,PID = %d\n", getpid());
execlp("ls", "ls", "-l", NULL);
} else if (pid > 0) {
// 父进程
printf("父进程,PID = %d\n", getpid());
wait(NULL); // 等待子进程结束
} else {
printf("Fork failed!\n");
}
return 0;
}
1.2 进程回收机制
子进程终止时,内核会将其状态保留(称为“僵尸进程”),直到父进程调用wait()或waitpid()将其资源释放。
情况 | 处理方式 |
---|---|
父进程调用 wait() |
内核回收子进程资源 |
父进程未调用 wait() |
子进程成为“僵尸进程”,资源占用不释放 |
父进程提前结束 | 子进程被 init 进程收养,由它负责回收 |
1.3 源文件变成可执行程序的过程
整个流程可以分为四个阶段:预处理、编译、汇编、连接。
1.3.1 详解
阶段 | 工具 | 输入 | 输出 | 说明 |
---|---|---|---|---|
预处理 | cpp |
.c 文件 |
.i 文件 |
展开宏、处理头文件 #include 、删除注释 |
编译 | cc1 |
.i 文件 |
.s 文件 |
将 C 源代码翻译为汇编语言 |
汇编 | as |
.s 文件 |
.o 文件 |
汇编器把汇编代码翻译为机器码的目标文件 |
链接 | ld |
.o 文件和库文件 |
可执行文件 | 将多个 .o 文件和库链接成一个完整的可执行程序 |
1.3.2 gcc命令细化
命令 | 说明 |
---|---|
gcc -E hello.c -o hello.i |
仅预处理 |
gcc -S hello.i -o hello.s |
编译为汇编 |
gcc -c hello.s -o hello.o |
汇编为目标文件 |
gcc hello.o -o hello |
链接成可执行文件 |
1.3.3 可执行文件加载为进程的过程(Linux 视角)
当你运行一个可执行程序,Linux内核将执行以下操作:
1)shell调用execve()系统调用
2)内核解析ELF文件结构
验证头部信息
读取程序头表(Program Header Table)
3)内核为进程分配内存
映射代码段、数据段
设置堆栈
4)加载器准备环境
复制命令行参数和环境变量到新进程内存
设置入口地址
5)CPU切换到入口地址
开始执行用户代码(即main函数)
1.4 总结框架表
内容 | 描述 |
---|---|
进程创建方式 | fork、clone、exec 系列函数 |
进程回收 | wait/waitpid 回收子进程资源 |
编译四阶段 | 预处理 → 编译 → 汇编 → 链接 |
可执行程序加载 | ELF 分析 → 映射内存 → 准备堆栈 → 切换执行 |
系统调用接口 | 用户态通过 syscall 进入内核态 |
2,crontab、at命令(待补充,用于定时备份等)
3,进程间通信
3.1 信号量
3.1.1 定义
信号量(Semaphore)是一种特殊的整型变量,用于在进程或线程之间实现同步和互斥控制。
3.1.2 信号量的类型
类型 | 说明 |
---|---|
计数信号量(Counting Semaphore) | 允许多个资源同时被访问,如连接池、线程池 |
二值信号量(Binary Semaphore) | 取值仅为0或1,等价于互斥锁,用于互斥控制 |
3.2 信号量在进程间通信中的作用
场景 | 用途 |
---|---|
控制共享资源的访问 | 避免多个进程同时访问同一资源 |
同步操作 | 保证某些操作顺序执行 |
实现临界区(Critical Section) | 控制某代码块只能被一个进程进入执行 |
3.3 Linux下的信号量实现方式
机制类型 | 用于 | 接口 |
---|---|---|
System V 信号量 | 进程间通信 | semget 、semop 、semctl |
POSIX 信号量 | 进程/线程间 | sem_init 、sem_wait 等 |
3.4 System V 信号量详解(进程间通信重点)
3.4.1 使用步骤
步骤 | 函数 | 描述 |
---|---|---|
1 | semget |
创建或获取一个信号量集合 |
2 | semctl |
控制信号量的属性,如初始化、删除等 |
3 | semop |
P/V 操作(即 wait/signal 操作) |
3.4.2 核心结构和函数原型
senget(); 创建或获取信号量集合
int semget(key_t key, int nsems, int semflg);
key 标识唯一信号量集,可通过fork生成
nsems 创建信号量个数
semflg 权限设置,如IPC_CREAT
semctl(); 控制信号量(初始化、删除等)
int semctl(int semid, int semnum, int cmd, ...);
常用命令:
SETVAL:设置信号量的值
IPC_RMID:删除信号量
GETVAL:获取当前值
semop(); 信号量操作(P/V操作)
int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf {
unsigned short sem_num; // 信号量索引
short sem_op; //操作数(-1 p操作,+1 v操作)
short sem_flg; // 0或IPC_NOWAIT
};
3.4.3 典型代码示例(进程间同步)
例子:父子进程使用信号量同步输出
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
union semun {
int val;
};
int main(){
key_t key = ftok(".", 123);
int semid = semget(key, 1, IPC_CREAT | 0666);
// 初始化信号量0
union semun sem_union;
sem_union.val = 0;
semctl(semid, 0, SETVAL, sem_union);
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程执行完毕\n");
struct sembuf sb = {0, 1, 0}; // v操作
semop(semid, &sb, 1);
exit(0);
} else {
// 父进程等待子进程完成
struct sembuf sb = {0, -1, 0}; // p操作
semop(semid, &sb, 1);
printf("父进程检测到子进程已完成,继续执行\n");
semctl(semid, 0, IPC_RMID); // 删除信号量
wait(NULL);
}
return 0;
}
3.5 信号量使用中的常见注意事项
问题 | 原因或建议解决方式 |
---|---|
死锁 | 同步顺序不一致,需避免循环等待 |
忘记释放资源 | 异常退出未执行 V 操作,建议设置信号处理 |
IPC 对象未清理(内核残留) | 使用 ipcs 查看,用 ipcrm 删除 |
并发性能瓶颈 | 使用共享内存 + 原子操作优化性能 |
生产环境中更推荐 POSIX 信号量
4,POSIX 信号量
POSIX信号量是基于POSIX标准定义的一种同步机制,支持:
命令信号量(适用于进程间)
匿名信号量(适用于线程间或共享内存中的进程)
与System V不同,POSIX信号量接口更贴近现代C编程,支持资源释放自动化(通过sem_close, sem_unlink)。
4.1 POSIX信号量头文件与链接方式
#include <semaphore.h> // 所有POSIX信号量接口
#include <fcntl.h> // O_CREAT 等
#include <sys/stat.h> // S_IRUSR等
编译时需要链接 -pthread或-lrt
gcc demo.c -o demo -pthread
4.2 POSIX信号量类型
类型 | 适用范围 | 创建方式 |
---|---|---|
命名信号量 | 多进程共享 | sem_open |
匿名信号量 | 多线程或共享内存 | sem_init |
4.3 常用函数接口(详细对照表)
函数名 | 用法描述 |
---|---|
sem_init |
初始化匿名信号量 |
sem_destroy |
销毁匿名信号量(与 sem_init 配合) |
sem_open |
打开或创建命名信号量 |
sem_close |
关闭命名信号量(与 sem_open 配合) |
sem_unlink |
删除命名信号量名称 |
sem_wait |
P 操作,等待(阻塞直到信号量值 > 0) |
sem_trywait |
非阻塞的 P 操作 |
sem_post |
V 操作,释放 |
sem_getvalue |
获取当前信号量的值 |
4.4 使用命名信号量(适用于进程间)
4.4.1 创建和初始化
sem_t *sem = sem_open("/mysem", o_CREAT, 0666, 1);
/mysem:信号量名称(必须以 / 开头)
0666: 权限
1: 初始值
4.4.2 使用信号量(同步)
sem_wait(sem); // p操作(阻塞)
sem_post(sem); // v操作(释放)
4.4.3 关闭与释放资源
sem_close(sem); // 关闭描述符
sem_unlink("/mysem"); // 从系统中删除该命名信号量
4.4.4 示例:两个进程同步输出
父进程代码(writer.c)
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
sem_t *sem = sem_open("/syncsem", O_CREAT, 0666, 0);
printf("父进程准备工作完成,等待子进程信号。。。\n");
sem_wait(sem); // 等待子进程完成任务
printf("父进程收到子进程信号,继续执行。。。\n");
sem_close(sem);
sem_unlink("/syncsem");
return 0;
}
子进程代码(reader.c)
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
#included <stdlib.h>
int main()
{
sem_t *sem = sem_open("/syncsem", 0);
sleep(2); // 模拟处理
printf("子进程完成任务,通知父进程。。。\n");
sem_post(sem); // 释放信号量
sem_close(sem);
return 0;
}
4.5 使用匿名信号量(适用于线程或内存共享)
4.5.1 初始化匿名信号量
sem_t sem;
sem_init(&sem, 0, 1); // 第二参数为0表示线程间使用;为1表示可用于进程间(共享内存中)
4.5.2 使用方式
sem_wait(&sem);
sem_post(&sem);
4.5.3 销毁信号量
sem_destory(&sem);
4.5.4 示例:线程互斥访问共享资源
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unsitd.h>
sem_t sem;
void *thread_func(void *arg) {
sem_wait(&sem);
printf("线程 %ld 正在访问共享资源\n", pthread_self());
sleep(1);
printf("线程 %ld 释放共享资源\n", pthread_self());
sem_post(&sem);
return;
}
int main()
{
pthread_t t1, t2;
sem_init(&sem, 0, 1); // 匿名信号量,线程间使用
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&