1.linux的内核链表(为什么一个进程可以在多个数据结构的原因)
1.不同于传统的直接给每一个结点直接设计_next和_prev指针,内核中将_next和-prev指针封装成一个独立的结构体并封装在每一个结点中。
也就是每一个指针的结构体指向的是对方的结构体而非整个结构体本身,也就是功能从指向整体到指向局部成员了。
有struct的成员地址从上往下依次增大且struct的地址与第一个成员的地址在大小上相同。
取整个struct成员的方式:
//links代表的是该指针结构体的变量名的统称,
//此处计算的是links到结构体第一个成员地址的偏差值(offset)
((struct task_struct*)0->links)
//_next/list,其中list代表的是头结点存的地址,
//_next指的是下第一个结点的地址
//二者相减得到该结构体的空间开始地址
_next/list - &((struct task_struct*)0->links)
//强转类型得对应类型的指针
(struct task_struct*)(_next/list - &((struct task_struct*)0->links));
而一个进程中可以创建很多个struct list_head,用不同的变量名表示区分就可以将该进程连接到不同的数据结构中。
因此进程能同时在双链表和调度队列的与原因就是二者的struct list_head不同。
2.linux的进程状态
linux中的进程状态是存在一个字符数组中的:
static const char *const task_state_array[] = {
"R (running)", /*0 */
"S (sleeping)", /*1 */
"D (disk sleep)", /*2 */
"T (stopped)", /*4 */
"t (tracing stop)", /*8 */
"X (dead)", /*16 */
"Z (zombie)", /*32 */
};
(1)
//&可以将该进程放在后台运行
(可执行程序).exe &
//proc中的STAT的状态显示中的状态有+好代表在前台运行,没有代表在后台运行。
例:
//前台运行
2361 2709 2709 2361 pts/0 2709 R+ 0 0:00 ps axj
//后台睡眠
1209 14595 1209 1209 ? -1 S 89 0:00 pickup -l -t unix -u
(2)
t是暂停,例:debug下的断点(即进程被暂停了)
T也是暂停,例:进程运行时用户用ctrl+z强制暂停时的状态就为T,还有以一种情况就是OS认为该进程有问题但需要用户来决定是否需要中止该进程时的暂停也是T。(给用户作决策时处于的暂停状态)
(3)
s和D都是阻塞状态,只是s是浅睡眠,而D是深睡眠。
(4)
x就是死亡状态。
(5)
z:僵尸状态。(x,z都算是结束状态)
介绍:我们创建子进程的目的是为了让子进程完成某种事情,而父进程就需要得到子进程的相关信息,因此在子进程被杀死前给父进程传输数据时的状态就叫僵尸进程。
子进程结果的相关信息存在task_struct中,即子进程的数据和代码别删除了其的task_struct还会存在一段时间依然其他进程读取。
如果父进程一直不管,不回收,不获取子进程发出的信息,那么该子进程的尸体就会一直存在。(也就是内存会被一直占用发生内存泄漏)
(6)
挂起状态,用户不关心,OS也不会提供呈现。
当进程退出时,内存会被OS回收,自然不存在内存泄漏的问题。
3.内核结构的资源申请
每一个PCB的创建就要malloc一块空间,不用时就释放,但当下次使用时又要重新malloc,对于PCB这种会被大量使用的资源。OS采用了对处于x状态的PCB用一个叫unuse的链表进行管理存储,当OS想要新的PCB时就将unuse的旧PCB更新一下后再使用。这种也叫数据结构对象的缓存(slab)
孤儿进程:父子关系中,如果父进程先退出,子进程就会被1号进程领养,这个被领养的子进程的状态就是孤儿进程。(系统就是1号进程,叫systemd)
领养的目的是为了保证子进程能顺利从僵尸进程中死去且领养后很快就变成了后台进程了(即无法用ctrl+c杀死该进程,只能kill -9 指定杀死了)