目录
第一部分、进程管理
进程与线程
-
基本认识
-
(1)进程是程序运行的实例,内部保存了程序运行所需的资源。
-
(2)线程是进程的一个实体,是CPU调度的基本单位。线程只拥有运行必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属于同一个进程的其他线程共享进程的资源。
-
-
多线程与多进程的区别(稳定性,通讯,切换)
-
(1)系统开销
-
维护多进程的系统开销大:包括进程的创建、销毁、切换等(栈、寄存器、虚拟内存、文件句柄等)。
-
维护多线程的系统开销小:只需要维护程序计数器、寄存器和栈即可。
-
-
(2)通讯开销
-
进程采用IPC进行通讯,相对复杂。
-
线程共享进程内存,通讯相对简单,但同步机制要求高。
-
-
(3)稳定性
-
进程稳定性高,进程间相互独立,一个进程崩溃一般不会引起另一个进程崩溃。
-
线程稳定性低,一个线程崩溃,所属进程的所有线程都将崩溃。
-
-
-
多线程
-
(1)线程数不能过多,因为线程采用轮询机制,线程过多将导致延迟增大,并且上下文切换频繁,会导致过多的CPU时间使用在切换上下文的操作上,降低系统的效率。
-
进阶认识
-
(1)进程控制块PCB:OS为每一个进程维护一个PCB,用来保存该进程有关的各种状态信息。
-
(1-1)进程标识信息:~本进程标识、~父进程标识、~用户标识等
-
(1-2)CPU状态保护区:保存进程的运行现场信息
-
用户可见寄存器:用户程序可以使用的数据,地址等寄存器。
-
控制和状态寄存器:程序计数器、程序状态字。
-
栈指针:过程调用、系统调用、中断处理和返回时需要使用。
-
-
(1-3)进程控制信息
-
-
(2)中断:CPU停止运行当前进程,转而去做别的事情。
-
(2-1)中断向量:中断服务程序的入口地址。
-
(2-2)发生中断后,OS最底层做了什么?(粗略)
-
1、保存中断现场。
-
2、转入中断服务程序。
-
3、处理中断。
-
4、调度下一次运行的进程。
-
5、恢复中断现场。
-
-
-
(3)进程挂起:把一个进程从内存转到外存
-
目的:充分合理使用系统资源。
-
阻塞挂起状态:进程在外存并等待某个事件的发生。
-
就绪挂起状态:进程在外村并等待CPU和内存。
-
-
(4)调度算法:
-
批处理系统:~先来先服务、~短作业优先(实际无法预先知道进程运行时间)、~最短剩余时间优先(抢占式,短作业优先的改版)
-
交互式系统:~时间片轮转法、~优先级调度(结合使用相同的时间片大小)、~多级反馈队列(在优先级调度的基础上,给进程不同大小的时间片)
-
~多级反馈队列:通过给予进程指数递增的时间片大小,来避免频繁多次上下文切换。例如:进程需要100个时间片,在优先级调度算法中,可能需要100次进程上下文切换,而多级反馈队列中,最多只需要7次进程上下文的切换。
-
-
实时系统:~单调速率调度、~最早截止时间调度
-
单调速率调度:抢占式、静态优先级、周期越短,优先级越高。
-
最早截止时间调度:根据截止时间动态分配优先级,截止时间越早优先级越高。
-
-
-
(5)操作系统如何完成进程调度:
-
OS中存在就绪队列和阻塞队列,每个队列中有进程的状态信息,当进程状态发生变化时,就从相应的队列中取下进程PCB,放入其他队列或者放入CPU运行。
-
-
(6)线程分类:~用户线程、~内核线程
-
-
进程的通讯方式
-
由于各个进程不共享相同的地址空间,任何一个进程的全局变量在另一个进程中都不可见,所以如果想要在进程之间传递数据,就需要通过内核,在内核开辟一块区域(缓冲区),使得该区域对多个进程都可见。
-
协程 Coroutine
-
(1)进程切换 与 线程切换
-
进程切换分两步:
-
1-1、切换页目录,以使用新的地址空间。
-
1-2、切换内核栈和硬件上下文。
-
-
线程切换:
-
2-1、切换内核栈和硬件上下文。
-
-
-
(2)协程
-
2-1、协程是一种 用户态 的轻量级线程,它的调度完全依赖用户的操作。
-
2-2、协程拥有自己的寄存器上下文和“栈”(可以共享栈且有stackless与stackful实现方式)。
-
2-3、上下文的切换非常快。
-
2-4、同步逻辑写代码,异步执行程序。
-
-
(3)为何使用协程?
-
3-1、使用异步的优势,避免IO操作阻塞线程。
-
当协程挂起时,应该时当前协程发起异步操作的时候。
-
当协程唤醒时,应该在其他协程退出,且当前协程异步操作完成的时候。
-
-
3-2、“硬件上下文切换” --> 使用内存栈保存寄存器内容(386 -- 8 个寄存器, x86-64 14)
-
上下文切换时,使用 寄存器 与 内存 之间的切换。
-
-
管道
-
基本认识
-
(1)匿名管道,父子等有血缘关系的进程所有,半双工通讯。
-
(2)命名管道,内核所有,知道管道ID的进程即可使用,需要同步机制。
-
-
使用方法
-
(1)匿名管道,数据读出多少,管道内容减少多少。
-
int pipe (int file_descriptor[2]); // 返回值:成功:0,失败:-1 // file_description 表示文件描述符,由用户指定。
-
管道实例代码,附录—PIPE
-
(1)管道为空时读取,即还没来得及写入时。附录—PIPE_EMPTY_READ
-
(1-1)结论:阻塞模式下,进程将阻塞,直至管道被其他进程写入数据后,才能继续读取。
-
(1-2)结论:使用fcntl修改管道为非阻塞,则进程直接退出,read返回-1,设置errno = EAGAIN。
-
-
(2)管道为满时写入,即还没来得及读出时。附录—PIPE_FULL_WRITE
-
(2-1)结论:阻塞模式下,进程将阻塞,直至管道内容被其他进程读出后,才能继续写入。
-
(2-2)结论:使用fcntl修改管道为非阻塞,则进程直接退出,write返回-1,设置errno = EAGAIN。
-
-
(3)所有管道写端关闭时读取,结论:将把管道内的所有数据全部读出,最后read返回值未0后结束;若存在写端未关闭,则会进程会再度进入阻塞状态。
-
(4)所有管道读端关闭时写入,结论:无法写入,理解:没有人能读取信息,没必要写入。
-
(5)PIPE_BUF与原子性问题:
-
小于等于PIPE_BUF的写操作必须时原子的:要写的数据将被连续地写入管道。
-
大于PIPE_BUF的写操作写入时,不保证写入的原子性。
-
-
-
平时经常使用。
-
ls | grep xx
-
-
(2)高级管道(略)。
-
(3)命名管道,有权限的非亲缘进程也可以通过pathname访问。
-
int mkfifo(const char * pathname, mode_t mode); // 返回值:成功:0,失败:-1 // pathname 表示文件的路径名称 // mode 表示访问权限
-
该函数会生成一个管道文件(标识符: p ),对其的访问,与普通文件访问形式一致。
-
-
消息队列
-
基本认识:
-
将消息挂在内核区域内,然后进程可以根据消息类型进行读取,不必采用管道那样先进先出的方式。
-
消息队列是存放在内核中的消息链表,每个消息队列链表会由消息队列标识符标识。
-
-
使用方法:
-
(1)创建、访问消息队列
-
int msgget(key_t key, int msgflag); // 返回值:成功,消息队列ID,失败,-1 // key 表示 消息队列的唯一标识 // msgflag 表示 消息队列的权限
-
-
(2)发送消息到消息队列
-
int msgsend(int msgid, const void * msg_ptr, size_t msg_sz, int msgflg);
-
-
(3)接受消息从消息队列
-
int msgrcv(int msgid, void * msg_ptr, size_t msg_sz, long int msgtype, int msgflag);
-
-
(4)控制消息队列
-
int msgctl(int msgid, int command, struct msgid_ds *buf);
-
-
-
案例 msg
-
共享内存
-
基本认识
-
(1)物理内存上的一块区域被映射到不同进程的进程地址空间上,被不同的进程共享。
-
(2)进程对内存可以直接进行修改,因此需要一定的同步机制。
-
-
使用方法
-
(1)构造共享内存
-
#include <sys/shm.h> int shmget (key_t key, size_t size, int shmflg); // 返回值:成功:共享存储ID,指向共享内存首地址,失败:-1 // key 表示 共享内存的唯一标识符,与id类似,但两者不同 // size 表示 共享内存的大小 // shmflg 表示 共享内存的访问权限
-
-
(2)访问共享内存
-
void * shmat (int shmid, const void * shmaddr, int shmflg); // 返回值:成功:void*,指向共享内存段首地址,失败:-1 // shmid 表示 共享内存的id, 从shmget的函数返回值中获得 // shmaddr 表示 共享内存映射到当前进程的内存空间的起始位置,一般为NULL,表示自动选择映射的地址 // shmflg 表示 标志位,一般为0
-
-
(3)分离共享内存
-
int shmdt (const void * addr); // 返回值:成功:0,失败:-1 // addr 表示 共享存储段的指针,从shmat的函数返回值获得
-
-
(4)控制共享内存(删除)
-
int shmctl (int shmid, int cmd, struct shmid_ds * buf) // 返回值:成功:0,失败:-1 // shmid 表示 共享内存的id, 从shmget的函数返回值中获得 // cmd 表示 对共享内存采取的操作,与buf搭配使用 // buf 表示共享内存基本属性的结构体 // cmd = IPC_STAT : 获取内存属性,赋值给buf // cmd = IPC_SET : 设置内存属性,从buf中获取设置参数 // cmd = IPC_RMID : 删除共享内存
-
-
(5)共享内存属性
-
// 包括:访问权限、内存大小、创建者PID、最后操作内存者PID、连接数量等
-
-
(6)命令行操作
-
# 查看 -m 表示共享内存,不加则显示所有ipc ipcs -m # 删除 ipcrm -m shmid
-
-
-
共享内存实例代码,附录—MEMORY_SHARED
IPC小结
僵尸进程
-
基本认识
-
(1)一个进程结束后,它所占有的资源没有被父进程回收并释放,就成为僵尸进程。
-
(2)进程仍占有的是内核资源,如PCB,用户空间资源在退出之前已经完全释放。
-
-
避免僵尸进程的方式
-
(0)具体查看僵尸进程方式
-
ps -u # STAT 为 Z的即为僵尸进程
-
-
(1)fork twice. 附录—FORK_TWICE
-
(1-1)父进程比子进程更早终止,则Init进程会接管子进程,进行善后处理。
-
(1-2)父进程wait子进程,可以直到子进程的状态,只是为了释放其资源则不需要参数。
-
// 1. 手动,wait成功返回子进程ID while (pid != wait()) // pid记录想要等待的子进程号 // 2. 调用API pid_t waitpid(pid_t pid, int *statloc, int options); // 成功返回子进程id,失败返回-1。 // pid == -1,为任意子进程
-
-
-
(2)捕获SIGCHLD信号,并在信号处理函数中wait子进程 (classic way)附录—CATCH_SIGCHLD
-