《Linux内核设计与实现》Chapter 3 读书笔记

《Linux内核设计与实现》Chapter 3 读书笔记

进程管理是所有操作系统的心脏所在。

一、进程

1.进程就是处于执行期的程序以及它所包含的资源的总称。

2.线程是在进程中活动的对象。

3.进程提供两种虚拟机制:虚拟处理器和虚拟内存。

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

二、进程描述符及任务结构

内核把进程的列表存放在叫做任务队列的双向循环链表中。链表中的每一项都是类型为task_struct的进程描述符结构,该结构定义在<linux/sched.h>文件中。

1.分配进程描述符

Linux通过slab分配器分配task_struct结构——能达到对象复用缓存着色的目的。

用slab分配器动态生成task_struct,只需在栈底或者栈顶创建一个新的结构struct thread_info

2.进程描述符的存放

内核通过一个唯一的进程标识值PID来标识每个进程。PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型,最大值默认设置为32768。

3.进程状态

进程描述符中的state域是用来描述进程当前状态。

  • TASK_RUNNING(运行):进程是可执行的,或者正在执行,或者在运行队列中等待执行
  • TASK_INTERRUPTIBLE(可中断):进程正在睡眠,也就是被阻塞
  • TASK_UNINTERRUPTIBLE(不可中断):与可中断状态相同,接收到信号也不会被唤醒或准备投入运行
  • TASK_TRACED:被其他进程跟踪的进程
  • TASK_STOPPED(停止):进程停止执行;进程没有投入运行也不能投入运行。

4.设置当前进程状态

调整某个进程的状态,最好用set_task_state(task,state)函数。

功能:该函数将指定的的进程设置为指定的状态。

等价于

task -> state = state

5.进程上下文

陷入内核空间:从一个可执行文件载入到进程的地址空间执行。

进程只有通过接口才能陷入内核执行。

6.进程家族树

所有的进程都是PID为1的init进程的后代。

系统中的每一个进程必有一个父进程,可以拥有零个或多个子进程,拥有同一个父进程的进程叫做兄弟。

获得父进程的进程描述符:
    struct task_struct *my_parent = current->parent;

依次访问子进程:
    struct task_struct *task;
    struct list_head *list;

    list_for_each(list, &current->children) {
            task = list_entry(list, struct task_struct, sibling);
            /* task指向当前某个子进程*/
}
            

init进程的进程描述符是作为init_task静态分配的。

获取链表中的下一个进程: list_entry(task->tasks.next, struct task_struct, tasks) 获取链表中的前一个进程: list_entry(task->tasks.prev, struct task_struct, tasks) for_each_process(task)宏提供了依次访问整个任务队列的能力, struct task_struct *task; for_each_process(task) { /* 它打印出每个任务名称和PID*/ printk("%s[%d]\n", task->comm, task->pid); }

三、进程创建

fork()通过拷贝当前进程创建一个子进程。

(子进程与父进程的区别仅在于PID,PPID和某些资源和统计量)

exec()负责读取可执行文件并将其载入地址空间开始运行。

1.写时拷贝

写时拷贝是一种可以推迟甚至免除拷贝数据的技术,内核不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝。

资源的复制只有在需要写入时才会进行,在此之前以只读方式共享。

2.fork()

do_fork定义在kernel/fork.c文件中,调用copy_process

创建进程的大概步骤:
 1.fork()、vfork()、__clone()都根据各自需要的参数标志调用clone()。
 2.由clone()去调用do_fork()。
 3.do_fork()调用copy_process()函数,然后让进程开始运行。
 4.返回do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。

3.vfork()

与fork()功能基本相同。

优点:

不拷贝父进程的页表项

vfork()系统调用的实现是通过向clone()传递一个特殊标志来进行的。

  • 调用copy_process()时,task_struct的vfor_done成员被设置为NULL。
  • 在执行do_fork()时,如果给定特定标志,则vfor_done会指向一个特定地址。
  • 子进程先开始执行后,父进程不是马上恢复执行,而是一直等待,知道子进程通过vfor_done指针向它发送信号。
  • 在调用mm_release()时,该函数用于进程退出内存地址空间,并且检查vfor_done是否为空,如果不为空,则会向父进程发送信号。
  • 回到do_fork(),父进程醒来并返回。

四、线程在Linux中的实现

Linux把所有的线程都当做进程来实现。

1.创建线程

  • 在调用clone()需传递一些参数标志指明共享资源。
 clone(CLONE_VM | CLONE_FS | CLONE_SIGHAND, 0);

新建的进程和它的父进程就是线程。

  • 一个普通的fork()。
clone(SIGHAND, 0);
  • vfork()的实现。
clone(CLONE_VFORK | CLONE_VM | SIGHAND, 0);

clone()参数标志决定新创建进程的行为方式和父子进程之间共享的资源种类。

2.内核线程

内核线程没有独立的地址空间,只在内核空间进行。内核线程也只能由其他线程创建。

内核线程启动后就一直运行直到调用do_exit

五、进程终结

进程终结时,内核必须释放它所占有的资源并告知父进程。

原因:

一般是来自自身,发生在调用exit()系统调用时。

  • 显式的调用
  • 隐式的从某个程序的主函数返回

1.删除进程描述符

释放进程描述符时,需要调用release_task()

2.孤儿进程造成的进退维谷

如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态。

解决方法:

  1. 在当前进程组找一个线程作为养父
  2. 让init成为它们的父进程。
时间: 2024-10-01 04:55:58

《Linux内核设计与实现》Chapter 3 读书笔记的相关文章

《Linux内核设计与实现》 Chapter4 读书笔记

<Linux内核设计与实现> Chapter4 读书笔记 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统. 一.多任务 多任务操作系统就是能同时并发地交互执行多个进程的操作系统. 多任务系统可以划分为两类: 非抢占式多任务 进程会一直执行直到自己主动停止运行 抢占式多任务 Linux/Unix使用的是抢占式的方式:强制的挂起进程的动作就叫做抢占. 像所有unix的变体和许多其他现代操作系统一样,Linux提

《Linux内核设计与实现》Chapter 2 读书笔记

<Linux内核设计与实现>Chapter 2 读书笔记 一.获取内核源码 1.使用Git 我们曾经在以前的学习中使用过Git方法 $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 更新分支到Linux的最新分支 $ git pull 可以获取并随时保持与内核官方的代码树一致 2.安装内核源代码 压缩形式为bzip2 $ tar xvjf linux-x.y.z.tar.bz2 压缩

《Linux内核设计与实现》Chapter 5 读书笔记

<Linux内核设计与实现>Chapter 5 读书笔记 在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口的作用是: 使应用程序受限地访问硬件设备 提供创建新进程与已有进程进行通信的机制 提供申请操作系统其他资源的能力 一.与内核通信 1.系统调用的作用 系统调用在用户空间进程和硬件设备之间添加了一个中间层,作用是: 为用户空间提供了一种硬件抽象接口: 系统调用保证了系统的稳定和安全,即可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源: 每个进程都运行在虚拟系统中

读书笔记2013-2 Linux内核设计与实现A

<Linux内核设计与实现> 简介 这本书不是想Linux源码剖析那样一行行分析Linux源代码的书,而是从Linux历史,Linux哲学,Linux设计原理和原则,计算机硬件相关知识,编译安装内核实战等多方面多角度讲述和Linux相关的方方面面.从中学到的更多的不单内核的源代码,而是Linux的哲学.建议所有从事Linux相关工作的猿都要读一下,读完之后,很多东西都变得容易理解,知其然,知其所以然.如果没有读过<深入理解计算机>类似的图书,建议和<深入理解计算机>一起

【读书笔记】《Linux内核设计与实现》内核同步介绍&内核同步方法

简要做个笔记,以备忘. 需同步的原因是,我们并发访问了共享资源.我们将访问或操作共享资源的代码段称"临界区",如果两个执行线程处于同一临界区中同时执行,称"竞争条件".这里术语执行线程指任何正在执行的代码实例,如一个在内核执行的进程.一个中断处理程序或一个内核线程. 举个简单例子,i++操作.该操作可以转换为下面的机器指令序列: 1.得到当前变量i的值,并保存到一个寄存器. 2.将寄存器的值加1. 3.将i的新值写回到内存中. 当两个线程同时进入这个临界区,若i初值

Linux内核设计与实现 读书笔记 转

Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://blog.csdn.net/yrj/article/category/718110 Linux内存管理和性能学习笔记(一) :内存测量与堆内存 第一篇 内存的测量 2.1. 系统当前可用内存 # cat /proc/meminfoMemTotal:        8063544 kBMemFree:       

《Linux内核设计与实现》读书笔记(十一)- 定时器和时间管理

系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关概念 定时器执行流程 实现程序延迟的方法 定时器和延迟的例子 1. 系统时间 系统中管理的时间有2种:实际时间和定时器. 1.1  实际时间 实际时间就是现实中钟表上显示的时间,其实内核中并不常用这个时间,主要是用户空间的程序有时需要获取当前时间, 所以内核中也管理着这个时间. 实际时间的获取是在开机后,内核初始化时

《Linux内核设计与实现》读书笔记——第一二章

<Linux内核设计与实现>读书笔记——第一二章 第一章 Linux内核简介 1.1 Unix的历史 简洁:仅提供系统调用并有一个非常明确的设计目的. 抽象:Unix中绝大部分东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相同的系统调用接口来进行的(open().read().write().lseek().close()). 可移植:使用C语言编写,使其在各种硬件体系架构面前都具备令人惊异的移植能力. 进程创建迅速:有独特的fork()系统调用,一次执行保质保量地完成一个任务.简

《Linux内核设计与实现》读书笔记(八)- 中断下半部的处理

在前一章也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件. 主要内容: 中断下半部处理 实现中断下半部的机制 总结中断下半部的实现 中断实现示例 1. 中断下半部处理 那么对于一个中断,如何划分上下两部分呢?哪些处理放在上半部,哪些处理放在下半部? 这里有一些经验可供借鉴: 如果一个任务对时间十分敏感,将其放在上半部 如果一个任务和硬件有关,将其放在上半部 如果一个任务要保证不被其他中断打断,将其放在上半部 其他所有任务,考虑放在下半部 2. 实现中断下半部