嵌入式——进程与线程1

**

进程与线程1

**

📖今日学习内容:程序与进程概念、进程状态、fork创建子进程、进程回收wait/waitpid、exec函数族、环境变量;线程概念、线程创建回收、进程线程对比。

🔍文章大纲

  1. 基础概念:程序 vs 进程
  2. Linux进程常用shell命令
  3. 进程虚拟地址空间
  4. 进程五大状态
  5. 进程创建 fork()
  6. 进程退出 exit/_exit
  7. 进程回收 wait / waitpid,僵尸进程、孤儿进程
  8. exec函数族
  9. 环境变量 setenv / environ
  10. 线程基础:线程概念、进程与线程区别
  11. 线程API pthread_create / pthread_exit / pthread_join
  12. ✨学习重点汇总
  13. ⚠️易错点&踩坑记录
  14. 📝今日学习小结

1. 基础概念:程序 vs 进程

  • 程序:存放在外存(磁盘)上的可执行文件,静态,没有运行。
  • 进程:程序的一次动态执行实例,包含创建、调度、运行、消亡整个生命周期。>

PID:进程唯一标识ID;PPID:父进程ID。

2. Linux进程常用shell命令

命令 功能
ps -ef 查看系统全部进程,PID、PPID
ps -aux 查看进程CPU、内存占用
pstree 查看进程父子树关系
top 实时查看进程资源占用
kill -9 PID 强制杀死指定进程
killall -9 进程名 按名字批量杀进程
./a.out & 程序放到后台运行
jobs 查看后台任务
fg 编号 后台任务切回前台运行
nice / renice 修改进程优先级

3. 进程虚拟地址空间(32位0~4G)

每个进程拥有独立虚拟地址空间,MMU内存管理单元映射到物理内存。

分区:

  1. 文本段(.text):存放可执行代码、指令,只读
  2. 数据段
    • .data:已经初始化全局变量、static静态变量
    • .bss:未初始化全局、静态变量,程序启动自动置0
    • .rodata:字符串常量,只读
  3. 栈区(.stack):局部变量、函数形参、函数调用栈;函数退出自动回收;空间较小。
  4. 堆区(.heap) :程序员手动管理,malloc/free申请释放,存放大数据。

进程之间虚拟地址相互隔离,一个进程不能直接读写另一个进程的地址空间。

4. 进程6种状态

状态 标记 说明
运行态 R CPU正在执行该进程
就绪态 R 等待CPU时间片,资源就绪等待调度
可中断睡眠态(S) S 等待资源,可以被信号唤醒
不可中断睡眠态(D) D 等待硬件IO,不能被信号唤醒
停止态(T) T 收到暂停信号,进程暂停
僵尸态(Z) Z 子进程代码执行结束,资源未被父进程回收,残留PCB
结束态(X) X 资源全部回收,彻底消亡

僵尸进程 :子进程已经退出,父进程没有调用wait/waitpid回收子进程资源,产生僵尸进程,占用PCB资源。

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

5. 进程创建 fork()

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

返回值

  1. 父进程:返回子进程PID(>0)
  2. 子进程:返回 0
  3. 失败:返回 ‑1

fork工作原理:

  1. 调用fork之后,创建子进程,拷贝父进程的虚拟地址空间(写时复制COW)
  2. fork之后,父子进程从fork返回处,同时向下执行代码
  3. 文件描述符会被复制,父子共享同一个文件偏移。
  4. 父子进程是完全独立进程,变量互不干扰。

写时复制COW:刚fork时父子共享物理内存;只有任意一方对内存做写操作,才会真正拷贝内存页面。

6. 进程退出

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

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

7. 进程回收 wait / waitpid

wait --- 阻塞回收子进程

复制代码
pid_t wait(int *wstatus);
  • 阻塞等待任意一个子进程结束;
  • wstatus保存子进程退出状态,传NULL代表不关心状态。
  • 返回值:成功返回退出子进程PID;失败返回‑1。

状态解析宏:

  • WIFEXITED(wstatus):非0 → 子进程正常退出
  • WEXITSTATUS(wstatus):获取子进程exit的退出码
  • WIFSIGNALED(wstatus):非0 → 子进程被信号杀死
  • WTERMSIG(wstatus):获取杀死进程的信号编号

waitpid --- 指定PID回收,可以非阻塞

复制代码
pid_t waitpid(pid_t pid, int *wstatus, int options);

参数pid:

  • pid > 0:回收指定PID的子进程
  • pid = -1:回收任意子进程,等价wait
    options选项:
  • 0:阻塞模式
  • WNOHANG非阻塞,子进程未结束返回0,不卡住父进程

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

8. exec函数族

fork创建子进程后,子进程复制父进程代码;exec可以让子进程加载执行全新程序,替换当前进程地址空间,PID保持不变

常用原型:

复制代码
// l:参数列表传参;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[]);

特点:

  1. exec调用成功之后,后面代码全部不再执行,地址空间被新程序覆盖。
  2. exec只有失败才有返回值,成功无返回。
  3. 文件描述符默认继承。

9. 环境变量

  • 全局变量 extern char **environ; 保存全部环境变量。

    int setenv(const char *name, const char *value, int overwrite);

  • overwrite=1:存在就覆盖;overwrite=0,存在则不修改。

10. 线程基础

线程:轻量级进程LWP,进程是资源分配最小单位;线程是CPU调度最小单位

进程与线程对比

对比项 进程 线程
资源分配 每个进程拥有独立完整虚拟地址空间 线程隶属于进程,共享进程的堆、全局、data、bss、rodata ;每个线程拥有独立栈
调度单位 进程 线程(CPU调度)
切换开销 大,切换页表,刷新cache 很小,只切换栈、寄存器
隔离性 进程完全隔离,进程间默认不能直接访问 同进程内线程共享内存,直接读写全局变量,存在竞态条件
销毁影响 一个进程崩溃不影响别的进程 一个线程崩溃,整个进程全部直接崩溃

一个进程至少有1个主线程(main线程)。

11. POSIX线程API(pthread库,编译需要加 -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,主线程退出,其他子线程可以继续运行。
  3. pthread_join:阻塞等待指定线程结束,回收线程资源;如果不join,会产生僵尸线程。>

编译命令示例:gcc test.c -o test -pthread


✨学习重点

  1. 程序静态,进程动态;PID进程唯一标识,PPID父进程ID。
  2. 进程虚拟空间分区:text、data、bss、rodata、栈、堆;栈自动回收,堆需要malloc/free手动释放。
  3. fork:写时复制COW;返回三次(父>0,子=0,失败‑1),父子进程从fork之后并行执行。
  4. 僵尸进程:子进程退出父进程没有wait回收;孤儿进程父进程先死,被init收养,不会僵尸。
  5. exit库函数刷新缓冲区;_exit系统调用直接退出,不刷新缓冲区;main return等价exit。
  6. wait阻塞回收任意子进程;waitpid支持指定PID,支持WNOHANG非阻塞回收,解决僵尸进程。
  7. exec函数族:替换进程地址空间,PID不变,成功之后后续代码不运行,只有失败返回。
  8. 进程vs线程:进程是资源分配最小单位;线程是CPU调度最小单位
  9. 同进程线程:共享堆、全局变量;每个线程有自己独立栈;一个线程崩溃整个进程崩溃。
  10. pthread库编译必须加 -pthread;pthread_join回收线程,否则僵尸线程。

⚠️易错点汇总

  1. 混淆_exit()exit()_exit()系统调用,直接退出不刷新缓冲区,容易丢失输出。
  2. fork之后误以为父子进程先后执行,实际父子进程调度顺序由操作系统决定,顺序不确定。
  3. fork之后父子进程变量拷贝,子进程修改变量不会影响父进程,写时复制,各自独立。
  4. 僵尸进程不等于孤儿进程:孤儿进程被init自动回收,不会产生僵尸;只有子结束父不回收才僵尸。
  5. wait会阻塞父进程;想要不阻塞必须使用waitpid(..., WNOHANG)
  6. exec执行成功,后面代码不会跑;不要在exec后面写业务逻辑,只写错误处理。
  7. 线程易错:main函数return 0是结束整个进程,所有子线程全部直接销毁;pthread_exit(NULL)只退出主线程,子线程继续运行。
  8. pthread编译忘记加 -pthread,编译报错未定义引用。
  9. 不调用pthread_join,线程结束资源不释放,产生僵尸线程内存泄露。
  10. 同进程多个线程直接读写全局共享变量,没有同步保护,出现数据竞争、乱码逻辑错误。
  11. 误以为线程崩溃只杀死当前线程:同一个进程任意一个线程崩溃,整个进程全部退出
  12. fork复制文件描述符,父子进程共享同一个文件偏移,父子同时write会出现内容交织错乱。

📝今日学习小结

今天学习Linux进程与线程核心知识点:

  1. 进程是程序动态运行实例,拥有独立虚拟地址空间;掌握进程6种状态,分清僵尸进程、孤儿进程产生原因。
  2. fork创建子进程,写时复制;exit/_exit进程退出;wait/waitpid回收子进程消除僵尸进程。
  3. exec函数族替换进程镜像,在不改变PID情况下执行全新程序。
  4. 线程是轻量级进程,CPU调度最小单位;线程共享进程大部分资源,每个线程拥有独立栈。
  5. pthread线程库,pthread_create创建,pthread_exit退出,pthread_join回收,编译链接必须带上‑pthread
相关推荐
DevHub25 分钟前
QEMU + Busybox 搭建嵌入式 Linux 开发环境:内核编译到启动,30 秒一个迭代
linux·运维·服务器
ouynagda25 分钟前
Linux 进程管理详解:从概念到实践
linux·运维·网络
2401_8906034025 分钟前
Linux 基础文件IO
linux·运维·服务器
tx112087635828 分钟前
K8S-pod管理与控制器管理
linux·运维·kubernetes·pod
老当益壮梁奶奶39 分钟前
Linux软件编程学习笔记(七):线程分离与线程间通信详解
linux·c语言·c++·笔记·学习
Awh-40 分钟前
Linux软件应用编程 第一章中:Linux文件操作
linux·运维·服务器
我命由我123452 小时前
UI 设计 7 种排列方式
xml·学习·ui·html·产品运营·产品经理·学习方法
Tim风声(网络工程师)2 小时前
Kail下载
linux·运维·服务器
常驻客栈3 小时前
B-深入理解计算机系统第3版之第8章:异常控制流
笔记·学习·常驻客栈·深入理解计算机系统·学习笔记‘’