Linux内核设计与实现_第三章 进程管理_学习笔记

3.1 进程

1) 程序与进程的区别
程序:就是程序正文与初始化的全局数据或静态数据,存放在磁盘上;
进程:exec将程序读入到内存空间,分配堆、栈、环境变量、bss数据段,内核再提供各种资源,使得程序得以运行,变成进程;(进程的其他资源:打开的文件, 挂起的信号, 内核内部数据, 处理器状态,寄存器值,线程等);
程序不是进程,进程是执行器的程序以及相关资源的总称;
2) 线程
线程拥有独立的程序计数器、栈、一组寄存器;
3) linux的线程实现很特别, 不对线程和进程区分。内核调度的对象是线程,而不是进程。线程是一种特殊的进程;
4) 进程提供两种虚拟机制:虚拟处理器和虚拟内存
虚拟处理器:时间分片+调度,使得进程独占CPU;
虚拟内存:虚拟地址空间+分页+页面调度, 使得进程好像独占内存空间;
5) 使用fork创建子进程,fork函数返回的那个点,父进程恢复运行,子进程开始执行(子进程不是从main函数起始处执行的,exec是从main函数处执行的);
6) fork函数, vfork函数实际上都是由系统调用clone()实现的,clone系统调用可以选择父子进程共享哪些资源;

3.2 进程描述符及任务结构

1) 内核维护一个叫做任务队列的双向循环链表,链表每一项都是进程描述符task_struct.
2)linux 通过slab分配器分配task_struct结构, 旧版本的内核中, task_struct存放在内核栈的尾部,这样可以使得一些寄存器资源少的机器只要通过栈指针就可以计算出进程描述符位置,避免了使用寄存器存放task_struct的指针; 现在由于动态分布task_struct,需要在内核栈栈底创建一个新的结构struct thread_info,内含进程描述符的地址;
3) 内核需要存储PCB信息, linux内核是支持不同体系的,但是不同体系结构可能进程需要的存储信息不相同。我们需要将与体系结构相关的部分与无关部分分离;
task_struct 保存了与硬件体系无关的信息; thread_info保存了与硬件体系相关的信息;
在这里插入图片描述
4) current宏表示当前进程; linux内核要取得正在运行额定进程描述符的速度很重要;硬件体系不同,current宏的实现也不同; 有的硬件体系使用专门的寄存器保存当前进程task_struct的指针(PowerPC); 有的机器(x86)在内核栈尾部创建thread_info结构,通过计算偏移间接查找task_struct;
5) 假定栈的大小是8K, 考虑分页机制,将栈指针后面13有效位屏蔽,就得到栈页的页首地址,也就是thread_info 结构的地址;current_thread_info()->task; 得到进程描述符task_struct的指针;
6) 进程状态
五种进程状态
7) 内核调整某个进程的状态

set_task_state(task,state);// 等价task->state = state;
set_current_state(state); // 等价 set_task_state(current, state);

8) 系统调用和异常处理程序是对内核有明确定义的接口。 进程只有通过这些接口才能陷入内核执行——对内核的所有访问都必须通过这些接口;
9) 取得进程的父进程

struct task_struct *my_parent = current->parent;

10) 访问子进程

struct task_current *task;
struct list_head *list;
list_for_each(list, &current->children{
		task = list_entry(list, struct task_struct, sibling);
}

11) 访问链表中的上下进程

list_entry(task->tasks.next, struct task_struct, tasks);
list_entry(task->tasks.prev, struct task_struct, tasks);

12) 访问整个进程链表

struct task_struct *task;
for_each_process(task){
	printk("%s[d]\n", task->comm, task->pid);
}

3.3 进程创建

1) 写时复制
对于程序正文,父子进程共享;
对于数据段、堆、栈,父子进程为只读,一旦有一个进程写, 内核才进程复制。通常也是针对被写区域的数据复制一页;

2) fork的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。
3) linux通过clone()系统调用来实现fork(), 这个调用通过一系列的参数标志指明父、子进程需要共享的资源; fork(), vfork() 和 __clone() 库函数根据各自需要的参数标志调用clone(), 然后clone调用do_fork. do_fork 完成了进程创建的大部分工作, 然后调用copy_process函数让进程开始运行;
4) 内核倾向于选择让子进程首先执行,因为一般子进程会马上调用exec函数,这样可以避免写时拷贝的额外开销;
5) vfork(), 除了不拷贝父进程的页表之外, vfork和fork的开销一样。子进程作为父进程的一个单独线程在其地址空间中运行。父进程阻塞,子进程不能向地址空间写入;目前,vfork的好处仅仅限于不拷贝页表项;一般不建议使用vfork;

3.4 线程在linux中的实现

1) linux实现线程的机制非常独特。 从内核上讲,并没有线程这个概念。linux将所有的线程都当作进程来实现。 线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都有自己的task_struct. 线程在内核中,就是一个普通的进程;
2) 不同操作系统上的线程、进程
其他操作系统中,内核提供了专门支持线程的机制, 并将线程当作轻量级进程。线程被抽象成一种耗费较少资源,能迅速执行的单元;
对于linux, 线程是一种进程间共享资源的手段(linux本身的进程已经足够轻量了);
举例:创建一个包含四个线程的进程
其他系统创建一个进程,这个进程内部创建四个线程;
linux: 创建四个进程,即tast_struct结构,然后再四个进程之中创建资源共享机制;
3) 创建线程
fork() 等价于
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
//共享地址空间、文件系统资源、文件描述符、信号处理程序等;

普通fork的实现
clone(SIGCHILD, 0);
vfork() 的实现
clone(CLONE_VFORK, CLONE_VM, SIGCHILD, 0);
4) 内核线程
通过内核线程完成一些后台操作;
内核线程和普通进程间的区别在于内核线程没有独立的地址空间,内核线程只是在内核空间运行,从不切换到用户空间;
内核线程只能由其他内核线程创建;
linux中进程有命名,这个命名是一个字符串,在创建的时候由函数kthread_create传入;
kthread_create创建了线程后,线程调用wake_ip_process之后才会唤醒线程; 函数kthread_run() 是kthread_create 和 wake_up_process 的宏

3.5 进程终结

1) 实际上do_exit() 函数完成进程终结工作;
2) do_exit()之后, 线程已经僵死,系统还保留着进程描述符。保存子进程终结时候的进程描述符,是为了让系统在子进程终结时候依旧能够获得子进程的信息;因此, 进程终结时将清理工作和进程描述符的删除分开执行。父进程获得子进程终止的信息之后 或是通知内核其并不关注这些信息后, 子进程的task_struct结构才会被释放;
3) wait函数获取子进程的信息后, release_task() 函数最终释放进程描述符;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值