嵌入式学习——进程与线程2

【Linux C学习笔记】进程、线程、线程同步、进程间通信总结

📖今日学习:进程状态、fork、wait/waitpid、exec函数族;线程pthread全套API;线程互斥锁、死锁;进程间通信:无名管道、有名管道、信号。

🔍文章大纲

  1. 进程基础回顾
  2. fork创建子进程、写时复制COW
  3. 进程退出、僵尸进程、孤儿进程
  4. 进程回收 wait / waitpid
  5. exec函数族,环境变量setenv
  6. 线程基础:进程与线程对比
  7. POSIX线程API:pthread_create / pthread_exit / pthread_join
  8. 线程属性:分离属性
  9. 线程同步:互斥锁pthread_mutex
  10. 死锁产生四大条件与避免方法
  11. 进程间通信IPC:无名管道pipe、有名管道mkfifo
  12. 信号基础,signal信号捕捉
  13. ✨学习重点汇总
  14. ⚠️易错点&踩坑记录
  15. 📝今日学习小结

1. 进程基础回顾

  • 程序:存放在磁盘上静态可执行文件。
  • 进程:程序一次动态运行实例,拥有0~4G独立虚拟地址空间,由MMU映射到物理内存。
  • PID:进程唯一ID;PPID:父进程ID。

进程虚拟内存分区:

  1. .text文本段:存放代码指令,只读
  2. .rodata:字符串常量,只读
  3. .data:已初始化全局/静态变量
  4. .bss:未初始化全局、静态变量,程序启动自动置0
  5. .stack栈区:局部变量、函数栈帧,函数退出自动回收
  6. .heap堆区:malloc/free手动申请释放,程序员管理

进程6种状态:

状态 标记 说明
运行态 R CPU正在运行
就绪态 R 资源就绪,等待CPU时间片
可中断睡眠 S 等待资源,可以被信号唤醒
不可中断睡眠 D 硬件IO等待,不能被信号唤醒
停止态 T 收到信号暂停运行
僵尸态 Z 子进程结束,PCB资源未被父进程回收

孤儿进程:父进程先退出,子进程被init/systemd收养,孤儿不会成为僵尸进程

2. fork 创建子进程

复制代码
#include <unistd.h>
pid_t fork(void);

返回值

  1. 父进程返回:子进程PID(大于0)
  2. 子进程返回:0
  3. 出错返回:‑1

写时复制 COW(Copy‑On‑Write)

  • fork之后,父子进程一开始共享同一份物理内存页;
  • 只有任意一方发生写操作的时候,才真正拷贝内存页面;读操作完全共享。
  • 文件描述符会被复制,父子共享同一个文件偏移。

fork之后父子执行顺序完全由操作系统调度决定,代码不能假设谁先运行。

3. 进程退出

复制代码
#include <stdlib.h>
void exit(int status);    //库函数:刷新IO缓冲区,执行atexit钩子,再退出

#include <unistd.h>
void _exit(int status);   //系统调用:直接结束进程,**不刷新缓冲区**
  • status取值范围 0~255,退出状态,可以被父进程wait获取。
  • main函数内return n等价于exit(n);普通函数return只是退出函数,不会结束整个进程

4. 进程回收 wait / waitpid

wait --- 阻塞回收任意子进程

复制代码
pid_t wait(int *wstatus);
  • 阻塞,直到任意一个子进程结束;
  • wstatus保存子进程退出状态,传NULL代表不关心状态。

状态解析宏:

复制代码
WIFEXITED(wstatus)   //非0:子进程正常退出
WEXITSTATUS(wstatus) //拿到子进程exit的退出码
WIFSIGNALED(wstatus)//非0:子进程被信号杀死
WTERMSIG(wstatus)   //获取杀死进程的信号编号

waitpid --- 指定PID回收,支持非阻塞

复制代码
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • pid>0:回收指定pid子进程;pid=-1等价wait回收任意子进程
  • options:0阻塞;WNOHANG非阻塞 ,子进程未结束返回0,不会卡住程序。

wait / waitpid作用:回收子进程PCB,消除僵尸进程。

5. exec函数族

fork复制父进程地址空间;exec用来替换当前进程的地址空间,PID保持不变,加载执行全新程序。

复制代码
// l:列表传参;v:数组传参;p:搜索PATH;e:自定义环境变量
int execl(const char *path, const char *arg, ... , (char*)NULL);
int execlp(const char *file, const char *arg, ... , (char*)NULL);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
  • exec调用成功之后,后面所有代码不会执行;只有失败才会返回‑1。

环境变量操作

复制代码
extern char **environ;  //全部环境变量
int setenv(const char *name, const char *value, int overwrite);
// overwrite=1存在就覆盖;0存在则不修改

6. 线程基础

进程:操作系统资源分配最小单位 ;线程:CPU调度最小单位

进程与线程对比

对比项 进程 线程
资源 拥有独立完整虚拟地址空间 共享进程text/data/bss/heap;每个线程拥有独立栈
切换开销 大,切换页表,刷新cache 很小,只切换寄存器、栈
隔离性 完全隔离,一个崩溃不影响别的进程 同进程任意线程崩溃 → 整个进程直接崩溃
通信 需要IPC进程间通信 直接读写全局变量,存在资源竞争
  • 一个进程至少存在1个主线程(main线程)。

7. POSIX线程API

编译必须加 -pthread 链接线程库

复制代码
// 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

// 线程退出
void pthread_exit(void *retval);

// 阻塞回收线程
int pthread_join(pthread_t thread, void **retval);
  1. pthread_create:attr传NULL使用默认属性;arg传给线程入口函数。
  2. pthread_exit():只结束当前线程;main函数调用pthread_exit(NULL),主线程退出,子线程可以继续运行。
  3. pthread_join():阻塞等待线程结束,回收线程资源;不join会产生僵尸线程内存泄漏

8. 线程分离属性 detach

  • 分离状态的线程:线程退出后系统自动回收资源,不需要调用pthread_join

    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

9. 线程同步:互斥锁 mutex

多线程同时修改共享全局变量,产生资源竞争(竞态条件),需要互斥锁保护临界区。

复制代码
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);  //初始化锁

pthread_mutex_lock(&mutex);    //加锁,拿不到锁阻塞
//临界区:访问共享资源
pthread_mutex_unlock(&mutex);  //解锁

pthread_mutex_destroy(&mutex); //销毁锁

10. 死锁

死锁产生4个必要条件(全部满足才会死锁)

  1. 互斥条件:资源同一时刻只允许一个线程占有
  2. 请求保持:线程占有部分资源,同时请求别的被占用资源
  3. 不可剥夺:已经占有的资源,别人不能强行抢走
  4. 循环等待:多个线程形成环路等待资源

避免死锁方案

  1. 保证多个锁加锁顺序统一
  2. 使用pthread_mutex_trylock()非阻塞尝试加锁,拿不到直接释放已有锁
  3. 减少锁粒度,尽量少同时持有多把锁

11. IPC进程间通信

无名管道 pipe

只支持**有亲缘关系进程(父子、兄弟进程)**通信;半双工,一端读一端写。

复制代码
int pipe(int pipefd[2]);
// pipefd[0]:读端;pipefd[1]:写端

管道行为特点:

  1. 管道无数据,读操作阻塞;管道写端全部关闭,read返回0(读到末尾)
  2. 管道写满,write阻塞;管道读端全部关闭,继续写会产生管道破裂SIGPIPE信号,进程直接终止

有名管道 FIFO mkfifo

复制代码
int mkfifo(const char *pathname, mode_t mode);
  • 可以用于任意两个进程通信,无关进程也可以使用;
  • 必须读端、写端同时打开FIFO,否则open会阻塞等待另一端。

12. 信号 signal

信号是内核给应用程序的异步通知机制。

进程收到信号三种处理方式:

  1. 默认处理SIG_DFL

  2. 忽略信号SIG_IGN

  3. 捕捉信号,执行自定义信号处理函数

    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);

SIGKILL(9)、SIGSTOP(19) 不能被忽略、不能被捕捉。

常用信号:

  • SIGINTCtrl + c
  • SIGQUITCtrl + \
  • SIGTSTPCtrl + z
  • SIGPIPE:管道破裂
  • SIGALRM:闹钟定时信号
  • SIGCHLD:子进程退出信号

✨学习重点

  1. 进程虚拟内存分区:text / rodata / data / bss / stack / heap;栈自动回收,堆需要手动malloc/free
  2. fork写时复制COW;父子PID区分;父子调度顺序不确定。
  3. 僵尸进程:子结束父不回收PCB;孤儿进程被init收养不会僵尸;wait/waitpid回收消除僵尸。
  4. exit()刷新缓冲区;_exit()直接退出,不刷新缓冲区。
  5. exec替换进程地址空间,PID不变,成功后后续代码不执行。
  6. 进程是资源分配最小单位,线程是CPU调度最小单位;线程共享进程大部分资源,每个线程拥有独立栈;一个线程崩溃整个进程崩溃。
  7. pthread库编译必须带‑pthreadpthread_join回收线程,否则僵尸线程内存泄漏。
  8. 多线程访问共享全局变量必须互斥锁保护临界区,防止竞态条件。
  9. 死锁四大必要条件,掌握避免死锁手段。
  10. 无名管道pipe:仅亲缘进程,半双工;有名管道mkfifo支持任意进程。
  11. signal信号捕捉,SIGKILL、SIGSTOP无法捕捉忽略。

⚠️易错点汇总

  1. fork之后不要想当然认为父进程一定先运行,调度顺序由操作系统决定。
  2. 区分exit()_exit()_exit()不会刷新stdout缓冲区,printf内容丢失。
  3. wait会阻塞父进程;想要非阻塞回收必须使用waitpid(...,WNOHANG)
  4. exec执行成功之后,后面代码不会运行,exec后面只写错误处理。
  5. 线程易错:main函数写return 0会直接结束整个进程,所有子线程全部销毁pthread_exit(NULL)只退出主线程,子线程继续运行。
  6. 编译pthread代码忘记加‑pthread,报未定义引用编译错误。
  7. 不调用pthread_join,线程结束资源不释放,产生僵尸线程内存泄露;可以设置分离属性避免join。
  8. 互斥锁忘记unlock;异常分支路径没有解锁,造成死锁。
  9. 死锁:多把锁加锁顺序不一致,极易触发死锁。
  10. pipe管道:读端全部关闭还继续写,触发SIGPIPE信号,进程直接退出。
  11. mkfifo有名管道open的时候,只有一端打开会阻塞,读写两端必须都存在。
  12. 信号:SIGKILL(9)、SIGSTOP(19)不能用signal忽略或者捕捉,写了也无效。

📝今日学习小结

今天学习Linux进程线程与基础IPC通信:

  1. 掌握进程完整生命周期:fork创建、exit退出、wait/waitpid回收;分清僵尸进程、孤儿进程产生条件;exec替换进程镜像。
  2. 线程是轻量级进程,共享进程资源,栈私有;学会pthread创建、退出、回收;理解线程资源竞争,使用互斥锁保护临界区,理解死锁四大条件。
  3. IPC通信:无名管道适用于亲缘进程;有名管道FIFO支持任意进程;信号是内核异步通知,注意部分信号不能捕捉忽略。
相关推荐
给AI剪纸的打工人21 分钟前
OpenCode怎么接第三方API?自定义Provider与Responses配置实战
linux·数据库·人工智能
我爱cope22 分钟前
【计算机网络 | 网络层10:OSPF 与 BGP:自治系统内部和互联网之间如何路由?】
网络·学习·计算机网络·智能路由器
白猫不黑25 分钟前
网络空间安全/信息安全专业学习规划(超级详细版)
运维·学习·安全·web安全·网络安全·信息安全
HyperAI超神经34 分钟前
MiniMax H3 突破视频生成边界,音画内容一体生成;Ornith-1.5-35B-A3B 探索推理模型自进化训练新模式
人工智能·深度学习·学习·音视频·多模态·视频生成·推理模型
lingran__1 小时前
Linux 基础常用指令万字详解(下)|一切皆文件:重定向、管道、日志 与 Shell 原理
linux·运维·服务器·shell·管道·重定向·打包压缩
小小帅呀1 小时前
学习 VLA 第6天:SWIN VIT算法原理以及复现
学习·算法
平原20181 小时前
AI岗位需求增长244%背后:从任务重组到可验证学习闭环
大数据·人工智能·学习
ouynagda1 小时前
Linux信号与进程间通信学习笔记
linux·笔记·学习
祖力551 小时前
进程间通信(IPC机制):无名通道与有名通道
linux·运维·服务器·进程·通信·进程间通信·ipc