进程与线程
1. 进程
1.1. 程序操作
1.1.1. 创建
创建的是原进程的子进程,子进程会复制父进程的PCB(进程控制块),二者之间代码共享,数据独有,拥有各自的进程虚拟地址空间。
#include <unistd.h>
pid_t fork(void);
- 写时拷贝技术 子进程创建后,与父进程映射访问同一块物理内存,当父或子进程对值进行修改时,会给子进程重新在物理内存中开辟一块空间,并将数据拷贝过去。
- pid_t: 创建子进程失败,会返回-1; 创建子进程成功,父进程返回pid号,子进程返回0。
1.1.2. 终止
- 正常终止
- main函数返回
- 调用exit函数:
status
可以通过wait(int *status)
接收; 会刷新缓冲#include <stdlib.h> void exit(int status);
- 调用_exit函数:
status
可以通过wait(int *status)
接收; 不会刷新缓冲#include <unistd.h> void _exit(int status);
- 异常退出
- Ctrl+C
- 被信号终止
1.1.3. 等待
如果子进程先于父进程退出,而父进程并没有关心子进程的退出状况,从而无法回收子进程的资源,就会导致子进程变成僵尸进程。进程等待的作用就是父进程对子进程收尸!父进程通过进程等待的方式,回收子进程的资源,获取子进程的退出状态。
wait
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
- 利用阻塞,等待子进程退出。
- 一次只能等待一个
- pid: 成功,子进程的pid; 失败(没有子进程),返回-1;
- status: 进程退出信息
waitpid
//头文件同wait的头文件 pid_t waitpid(pid_t pid, int *status, int options);
- 返回值:
- 等待成功,返回pid
- options为WNOHANG时,没有等待到子进程,马上退出不阻塞,返回0
- 出错则返回-1
- pid:
- -1: 随便等待一个进程
- pid号: 明确指定一个进程等待
- options:
- 0: 阻塞等待,等待到一个就退出
- WNOHANG: 没有等待到子进程,马上退出不阻塞,返回0
- 返回值:
1.1.4. 进程退出信息status
- 低16位存放信息,高16位不用
- 高8位 : 退出码,
exit
的传入值或者return
值(status >> 8) & 0xFF;
- 低8位 : 异常退出信息
- 第7位: core dump标志位;0,不产生;1,产生
(status >> 7) & 0x1;
- 低7位: 子进程是否异常退出;0,正常;非0,终止的信号
status & 0x7F;
- 第7位: core dump标志位;0,不产生;1,产生
1.2. 进程状态
1.2.1. top
指令查看:
- R (running): PCB被放入CPU的可执行队列中;CPU上执行的RUNNING状态、而将可执行但是尚未被调度执行的READY状态
- S (sleeping): 睡眠状态,PCB被放入等待队列,可以通过中断唤醒。
- D (disk sleep):睡眠状态,不能中断被唤醒。内核的某些处理流程是不能被打断的。用来保护进程。
- T (stopped): SIGSTOP信号让进程进入暂停,SIGCONT信号能将进程恢复。
- t (tracing stop): 在被追踪时,进程暂停;典型应用就是 gdb断点 。调试进程退出,被调试的进程才能恢复TASK_RUNNING状态。
- Z (zombie): 进程退出后,PCB没有被回收。父进程没有对子进程收尸,就只能等父进程退出后,让爷进程来统一收尸。
- X (dead): 马上会被彻底销毁
1.2.2. 执行时的状态:
进程主要有三种状态:运行态、就绪态、阻塞态。
- 「运行态」和「就绪态」可以相互转换,通常由系统的进程调度引起的。
- 遇到阻塞代码,进程进入「阻塞态」,等待被唤醒;没有足够的运行资源时,进程会被挂起,进入「阻塞态」等待资源充足。
- 进程由「阻塞态」被唤醒,就会进入「就绪态」,等待被调度。
1.3. 进程间通信
1.3.1. 管道
父子进程间通讯。
1.3.2. 命名管道
去除了父子进程间通信的机制,通常用来汇聚多个客户端进程与服务端进程的通信。
1.3.3. 消息队列
独立于进程存在,进程间可以通过消息队列来传递数据,典型的模式是生产者-消费者模型。
1.3.4. 信号
一个进程可以给另一个进程发送信号SIGN
来触发某些操作。
1.3.5. 共享内存
多个进程上不同的地址空间可以映射到同一块物理内存上,实现数据的共享。因为不涉及数据的拷贝,所以这是一种高效的通信方式。需要注意的是,多个进程并行时,需要通过同步机制保护共享内存的访问。
1.3.6. Socket 通信
调用TCP
与UDP
进行通讯。
1.3.7. 信号量
用来统计资源数量的。
- 整型信号量
0
: 繁忙,没有资源可用。> 0
: 资源有空闲。- 不能为负值
- 记录型信号量
0
: 繁忙,没有资源可用,也没有人过来等待资源。> 0
: 资源有空闲。< 0
: 没资源,等待队列下有人在那儿等着。
1.4. 僵尸进程和孤儿进程
- 僵尸进程: 父进程运行完毕,没有对「已经退出的子进程」进行收尸。
- 孤儿进程: 当父进程退出了,但是它的「子进程还没有退出」,这些子进程就变成了孤儿进程。孤儿进程只是暂时的,系统会在父进程退出时启动寻父机制,为子进程找到一个新的父亲。
1.5. 进程同步
进程同步: 进程同步是指控制进程按照一定顺序执行。只有处于临界区(指访问共享内存的代码片段)的进程才需要同步。
- 忙等待互斥(自旋锁):当某个变量不满足条件时,会一直轮询直到变量值发送改变。用于忙等待的锁称为自旋锁。
- 信号量: 是一个整型变量,用来实现计数器功能,主要提供
down
和up
操作(即P
和V
操作),这两个操作都是原子性的。当执行down
操作使信号量值变为0
时,会导致当前进程睡眠,而执行up
操作+1
时,会同时唤醒一个进程。 - 管程: 管程是由一个过程、变量和数据结构组成的一个集合,把需要控制的那部分代码独立出来执行,它有一个重要的特性,同一时刻在管程中只能有一个活跃的进程。为了避免一个进程一直占用管程,引入了条件变量和 wait 和 signal 操作。当发生当前进程无法运行时,执行 wait 操作,将当前进程阻塞,同时调入在管程外等待的另一进程执行,而另一个进程满足条件变量时,会执行 signal 操作将正在睡眠的进程唤醒,然后马上退出管程。
- 改造线程互斥锁: 这个得看系统支不支持。
2. 线程与进程
线程:进程中活动的对象,是CPU
调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程会维护自己的堆,栈和寄存器值。线程由CPU
独立调度执行,在多CPU
环境下就允许多个线程同时运行。
进程: 资源分配的基本单位,程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程「就绪队列」,进程调度器选中它的时候就会为它分配CPU
时间,程序开始真正运行。
3. 进程和程序
- 一个进程是程序执行时的一个实例,一个程序可以对应多个进程,但一个进程只能对应一个程序。
- 进程是动态的,而程序是静态的。
- 进程有一定的生命期,而程序是指令的集合,本身无运动的含义。
4. 线程
4.1. 线程的意义
- 在一个进程中会存在多种活动任务,需要有多个独立调度的单元来使这些任务可以并行的执行,这些单元就是线程。
- 线程比进程更轻量,它们比进程更快的创建,也更容易撤销。线程间切换的开销也比进程小,由于进程拥有大量的资源,当切换到另一个进程的时候,需要保存当前进程的所有资源,而线程间的切换只需要保存当前堆栈和相应寄存器的内容。
4.2. 线程同步
- 互斥锁:
pthread_mutex_
那一套 - 条件量: 引入信号机制,优化后的抢锁机制。
- 信号量: 同进程。
- 自旋锁: 同进程。
5. 并发和并行
并发: 一段时间内可以同时运行多道程序,对于CPU而言就是程序来回切换。 并行: 多个CPU同时运行程序,真正物理上的并发。