《Linux内核设计与实现》学习笔记——进程管理

时间:2022-07-05 16:54:27

1. 进程的概念


进程(处于执行期的程序),一段可执行的程序代码+相关的的资源

程序:完全有可能存在两个进程共同执行同一个程序

资源包括:打开的文件,挂起的信号,内核的内部数据,处理器的状态,内存地址空间,一个或多个执行线程


线程(执行线程),都拥有一个独立的程序计数器,进程栈和一组进程寄存器。

内核的调度对象是线程而不是进程。

在线程之间,可以共享虚拟内存,但是每个都拥有各自的虚拟处理器。

对于Linux而言,线程只不过是一种特殊的进程(共享某些资源)。


2. 进程描述符及任务结构


进程描述符: 结构体task_struct,包含一个具体进程的所有信息:打开的文件,进程的地址空间,挂起的信号,进程的状态等。<linux/sched.h>

任务队列:保存进程描述符的双向循环链表。


1)进程的描述表分配和存放


Linux通过slab分配器分配task_strack结构,这样可以达到对象的复用(避免动态分配和释放所带来的资源消耗)和缓存着色。

task_strack结构的存放位置:


tread_info<asm/thread_info.h>),在分配task_strack结构的时候,在内核栈底创建一个tread_info结构。其中内部成员task指向当前进程的task_struct

《Linux内核设计与实现》学习笔记——进程管理

//通过hread_info->task 获取当前进程 current 
static inline struct task_struct *get_current(void)
return current_thread_info()->task;
//通过sp寄存器找到当前进程的 thread_info
static inline struct thread_info *current_thread_info(void)
register unsigned long sp asm ("sp");
return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));

2)进程的状态

《Linux内核设计与实现》学习笔记——进程管理

TASK_RUNNING :进程可执行,正在执行或者在运行队列中等待执行。

                              进程在用户空间中执行的唯一可能的状态

TASK_INTERRUPTIBLE:进程正在睡眠(被阻塞),等待某些条件完成。

                              进程可以被信号唤醒。

TASK_UNINTERRUPTIBLE:除了不会被信号唤醒之外,与TASK_INTERRUPTIBLE相同

TASK_STOPPED进程停止执行。


3)设置当前进程的状态


set_task_state(task,state) ;

set_current_state(state);


4)进程的家族树


所有进程都是PID1init进程的后代,每一个进程必须有一个父进程。

每一个task_struct都包含一个指向其父进程task_struct的指针parent。

还包含一个称为children的子进程链表。


3.进程的创建


fork通过拷贝当前进程,创建一个新进程,

exec负责读取可只执行文件,并将其载入到地址空间开始执行


1)写时拷贝(copy-on-write


fork函数使用写时拷贝页实现,在创建新进程的时候,不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制。

地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。


fork的开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。


2fork


Linux通过clone系统调用实现fork,通过一系列的参数标志来指明父子进程需要共享的资源。forkvfork__clone库函数都根据各自的需要的参数标志调用clone

clone去调用do_fork

do_fork调用copy_process函数,然后让进程开始运行。

copy_process函数:(创建唯一的进程描述符)

    1)调用dup_task_struct为进程创建一个内核栈、thread_info结构和task_struct

          此时,子进程和父进程的描述符完全相同

    2)检查当前用户所拥有的进程数目是否超出限制

    3)子进程部分描述符成员清0或设置为初始值,主要是统计信息。

    4)子进程的状态设置为TASK_UNINTERRUTIBLE,不会投入运行。

    5)调用copy_flags以更新task_structflag成员。

    6)调用alloc_pid为新进程分配一个有效的PID

    7)根据传递给clone的参数标志,copy_process拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。

    8)做扫尾工作,并且返回一个指向子进程的指针

do_fork函数:在copy_process成功返回后,新创建的进程被唤醒并投入运行,内核有意选择子进程首先执行。


3vfork


不拷贝父进程的页表项。

父进程被阻塞,直到子进程退出或者执行exec()


4.线程在Linux中的实现


线程机制,在同一程序内共享内存地址空间,共享打开的文件及其他资源。

Linux,线程仅仅被视为一个与其他进程共享某些资源的进程。


1)创建线程

在调用clone的时候需要传递一些参数标志来指明需要共享的资源

cloneCLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND0

与父进程共享地址空间,文件系统,打开的文件,信号处理函数及被阻断的信号


2)内核线程

完成内核在后台执行的一些操作,没有独立的地址空间,只在内核空间运行,从不切换到用户空间,可以被调度,可以被抢占。

内核线程的创建,只能由其他内核线程创建:

structtask_struct *kthread_create (int (*threadfn)(void *data), void *data,

constchar namefmt[], ….....)

新创建的进程处于不可运行的状态,需要调用wake_up_process明确地唤醒。


5.进程的终结


进程的析构是由自身引发的,发生在进程调用exit系统调用时。(C语言编译器会在main函数的返回点后面放置调用exit的代码)

大部分的工作都依靠do_exit来完成。进程所有的资源都被释放掉,进程不可运行并处于EXIT_ZOMBIE退出状态。

它现在占用的所有内存就是内核栈,thread_info结构和task_struct结构。用来向父进程提供信息。


1)删除进程描述符

在父进程获得已终结的子进程的信息后或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。


2)孤儿进程

如果父进程在子进程之前退出,给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。