1. 进程与线程
- 进程是资源分配的最小单元,线程是资源调度的最小单元,线程上下文切换的效率是高于进程的。
- 进程间的通讯是要借助操作系统实现的(管道,有名管道,消息队列,信号量,共享空间等等),线程由于共享内存空间,所以可以直接通信,当然也会带来资源共享的问题。
- 所以多线程更适合用于隔离型更高的,切换不那么频繁的应用,反过来就更适合多线程。
在 Linux 系统中,进程与线程都是由 PCB 即 task_struct 控制的,但是两者也有区别,进程的堆栈信息都是由 mm_struct 来描述的,但是线程通过 mm_struct 共享内存空间,所以线程栈的信息不储存在 mm_struct 中,而是在 PCB 中,此外,线程栈是在线程创建时在进程栈中开辟出来的一块固定大小的区域,所以它失去了进程栈动态生长的特性。当然,我们提到的都是用户态栈,内核栈是创建在内核空间的,内核栈是与一个 thread_info 结构绑定在一起的。
2. 进程调度算法
Linux 采取的是 CFS 完全公平调度算法,在每个资源调度周期内,进程会按照权重均分时间片,以确保相对的公平性,这样也会有一个问题,如果过于频繁的切换,可能导致切换的开销远超运行成本,为避免这个情况,每个进程获得的时间片是有一个最小值的,另外,调度算法是借助 vruntime 与红黑树的结构实现的,进程调度器 每次在红黑树结构中取出 vruntime 最小的进程分配CPU资源。
实时调度算法的话,就有 FIFO,RR 等等了。
3. 进程生命周期
创建,就绪,运行,阻塞,停止。
在 Linux 中,运行与就绪的标识符都是 TASK_RUNNING,此外,阻塞有可中断与不可中断两种,区别是能否被信号伪唤醒,此外来还有终止与被追踪两种状态。
Linux 进程创建分为两步,fork() 与 exec(),第一步是拷贝父进程的 PCB 并且做微调修改,这时并不会立刻加载可执行文件等信息,而是等 exec() 才将这些信息加载进内存。
Linux 进程终止会导致僵尸进程与孤儿进程两种状态。子进程终止后,需要给父进程一个信号,父进程响应后才会销毁自己的 PCB,这个时候,如果父进程响应有问题,可能导致子进程的 PCB 一直存在,即僵尸进程,其会一直占据 PID 以及一定的内存资源,有害,需要手动杀死。如果父进程先于子进程死掉,子进程在销毁时会被判定为孤儿进程,这个时候会进行寻父,先找父进程的进程组,再找 init 进程,所以最终孤儿进程是可以找到养父正常终止的。
4. 中断
Linux 中断分为上部分与下部分,上部分是为了快速响应,是不能被抢占的,下部分进行耗时操作,可以借助软中断,tasklet或者任务队列实现,这一部分是可以抢占的。
5. 内存管理
Linux 的内存有三个分区,DMA 区提供给外设直接访问,Normal 区是内核空间可以直接访问,用户空间用三级页表的方式来管理。
Linux 内存分配有两个值得一提的特性,一个是 slab 高速缓存,会提前创建常用的数据结构体挂在队列上,另一个是伙伴系统,Linux 维护11个链表,每个链表挂在一种大小的内存块,需要分配内存的时候,取大于且最接近所需内存的块分配过去,这种方式没有外碎片,但是会有内碎片。
在内存与 CPU 寄存器中间还设有高速缓存,即快表,命中的话可以减少对对页表的访问,高速缓存采取“回写”策略,在修改时,并不是立刻写回内存,而是先标记,定期统一回写,另外高速缓存回收是 LRU 算法。
内存空间不足时会涉及页面置换算法,常用的是 LRU,CLOCK,二次机会法等等。