2017-2018-1 20179205《Linux内核原理与设计》第四周作业
《Linux内核原理与分析》
视频学习及实验操作
Linux内核源代码
视频中提到了三个我们要重点专注的目录下的代码,一个是arch目录下的x86,支持不同cpu体系架构的源代码;第二个是init目录下的main.c,是整个linux内核启动的起点,不过这里面不是main()函数,而是start_kernel,start_kernel函数相当于普通c程序的main函数,linux内核的核心代码在kernel目录中;最后是kernel 下进程调度相关的代码。
构造一个简单的Linux系统MenuOS
使用实验楼的虚拟机打开shell,输入命令,启动MenuOS系统:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
内核启动完成后进入menu程序,在qemu窗口下输入help,发现这个系统支持三个命令:help、version、quit:
使用gdb跟踪调试Linux内核的方法
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
关于-S -s选项的说明:
-S freeze CPU at startup (use ’c’ to start execution)
表示CPU开始初始化之前将其冻结,目的是阻止cpu执行接下来的指令
-s shorthand for -gdb tcp::1234
在tcp1234窗口上创建一个gdbserver作为通讯;若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
使用-S 之后,系统开始启动,qemu显示stopped,表示cpu已被冻结。
再打开另一个shell窗口,右键-水平分割,启动gdb来跟踪调试。输入:
file linux-3.18.6/vmlinux
target remote:1234
通过刚刚设置的端口号建立gdb和gdbserver之间的连接,我理解的是将下面这个窗口和上面冻结的窗口建立联系。接下来通过break设置断点,现在在内核启动的起点start_kernel函数处设置一个断点:
break start_kernel
我们可以通过按c,让冻结的qemu上的linux继续运行,此时发现,系统运行了一些命令在breakpoint1 start_kernel处停下了,说明断点设置有效。
同理可以设置第二个第三个断点,按c继续执行。
简单分析start_kernel
start_kernel在init目录下的main.c,整个start_kernel,就完成调用一系列的初始化函数,完成内核本身的设置:设置与体系结构相关的环境、进程调度器初始化、内存初始化等各种初始化。在Start_kernel函数的最后调用了rest_init()函数,在rest_init中建立了init线程,并在最后调用cpu_idle()函数。
对于视频中讲述的道生一,一生二,二生三,三生万物,我不是特别理解。从网上查了一下PID 0(idle), 1(init), 2(kthreadd)3个进程,有了大概的认识:
(1)init_task(0号进程的前世)在创建了init进程后,就变成了idle进程(0号进程的今生),执行一次调度后,切换当前进程到init;
(2)kernel_init(1号内核线程)执行内核的部分初始化工作及进行系统配置,变成了init用户进程;
(3)init_task(0号进程)又创建了Kthreadd内核线程(2号进程),它的任务是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数
教材第四章 进程调度
进程调度程序,是确保进程能有效工作的内核子系统。本章介绍了调度程序设计的基础和完全公平调度程序如何运用、如何设计、如何实现以及与它相关的系统调用。
4.1 多任务
多任务操作系统就是能同时并发地交互执行多个进程的操作系统。无论单处理器或者多处理器及其上,多任务操作系统都能使多个进程处于堵塞或者睡眠状态。
多任务系统划分为两类:非抢占式多任务和抢占式多任务。由调度程序决定什么时候停止一个进程的运行,以便其他进程能得到执行机会,这个强制的挂起动作叫抢占;相反,在非抢占式多任务模式下,除非进程自己主动停止运行,否则会一直执行下去,这个主动的挂起动作叫让步。 进程在被抢占之前能够运行的时间是预先设置好的,叫进程的时间片。
4.2 Linux的进程调度
O(1)调度器:对于大服务器的工作负载很理想,但缺少交互进程。
完全公平调度算法(CFS):“RSDL”,吸取了队列理论,将公平调度的概念引入Linux调度程序。
4.3 策略
策略决定调度程序在何时让什么进程运行,负责优化使用处理器时间。
4.3.1 I/O消耗型和处理器消耗型的进程
I/O消耗型:进程的大部分时间用来提交I/O请求或者等待I/O请求,经常处于可运行状态但是运行时间很短,等待更多的请求时最后总会阻塞。
处理器消耗型:把时间大多用在执行代码上,除非被抢占,否则通常都会不停运行。对于这类处理器消耗型的进程,调度策略通常会降低它们的调度频率,而延长其运行时间。
调度策略通常要在两个矛盾的目标中间寻找平衡:
·进程响应迅速(响应时间短);
·最大系统利用率(高吞吐量)
4.3.2 进程优先级
调度算法最基本的是基于优先级的调度,根据进程的价值和其对处理器时间的需求来对进程分级。通常按照优先级高低来运行,相同优先级的进程按轮转方式进行调度。
·nice值:
范围[-20,19],默认值为0;
nice值越大,优先级越低;
Linux系统中nice值代表时间片的比例;
ps-el命令查看系统中进程列表,NI列为nice值。
·实时优先级:
默认范围[0,99],值可以配置,;
值越高,优先级越高;
任何实时进程的优先级都高于普通的进程。
通过命令:
ps-eo state,uid,pid,ppid,rtprio,time,comm
查看到你系统中的进程列表,进程列表“-”说明它不是实时进程。
4.3.3 时间片
时间片是一个数值,是进程在被抢占前所能持续运行的时间。I/O消耗型不需要很长的时间片,而处理器消耗型的进程则希望越长越好。
Linux系统是抢占式的,是否要抢占当前进程,是完全由进程优先级和是否有时间片决定的。
CFS调度器:其抢占时机取决于新的可运行程序消耗了多少处理器使用比。
Linux调度算法
4.4.1 调度器
Linux调度器以模块方式提供,允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。每个调度器有一个优先级,会按照优先级顺序遍历调度类,选择优先级最高的调度器类。
完全公平调度CFS是一个针对普通进程的调度类。
4.4.3 公平调度
CFS做法允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,不再给每个进程分配时间片。在所有可运行进程总数基础上计算出一个进程应该运行多久。nice值作为进程获得的处理器运行比的权重。
4.5 Linux调度的实现
CFS调度算法实现的四个组成部分:
(一)时间记账
(二)进程选择
(三)调度器入口
(四)睡眠和唤醒
(一)时间记账
所有的调度器都必须对进程运行时间做记账。CFS不再有时间片的概念,但要使用调度器实体结构(定义在文件<linux/sched>的struct_sched_entity中)来追踪记账,进程描述符中的se变量。
CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久,定义在kernel/sched_fair.c文件中的update_curr()函数实现了该记账功能,update_curr()计算了当前进程的执行时间,并且将其存放在变量delta_exec中。
_update_curr(cfs_rq, curr, delta_exec)
update_curr()是由系统定时器周期性调用的,根据这种方式,vruntime可以准确地测量给定进程的运行时间。
(二)进程选择
CFS调度算法的核心是选择具有最小vruntime的任务,CFS使用rbtree来组织可运行进程队列,并利用其迅速找到最小vruntime值的进程。rbtree是一个自平衡二叉搜索树,是一种以树节点形式存储的数据,这些数据会对应一个键值,可以通过这些键值来快速检索节点上的数据,而且检索速度与整个树的节点规模成指数比关系。
挑战下一个任务:CFS运行rbtree树中最左边叶子节点所代表的那个进程。实现这一函数是__pick_next_entity():
这个函数不会遍历树找到最左叶子节点,该值缓存在rb_leftmost字段中,
函数返回值就是CFS选择的下一个运行进程。
如果返回NULL,表示树空,没有可运行进程,这时选择idle任务运行。
向树中加入进程:发生在进程被唤醒或者通过fork调用第一次创建进程时:
函数enqueue_entity():更新运行时间和其他一些统计数据,然后调用__enqueue_entity();
函数__enqueue_entity():进行繁重的插入工作,把数据项真正插入到rbtree中:
(把一个调度实体插入到rbrtree中)
static void _enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
从树中删除进程:删除动作发生在进程堵塞或终止时。和给红黑树添加进程一样,实际工作由辅助函数dequeue_entity()和__dequeue_entity()完成。
(三)调度器入口
进程调度的主要入口点函数是schedule():
schedule()函数会调用pick_next_task();
pick_next_task()会以优先级为序依次检查每一个调度类,并且选择最高优先级的进程;
pick_next_task()会返回指向下一个可运行进程的指针,没有时返回NULL;
pick_next_task()函数实现会调用pick_next_entity();
pick_next_entity()会调用__pick_next_entity()
(四)睡眠和唤醒
睡眠: 进程把自己标记成休眠状态,从可执行rbtree中移出,放入等待队列,然后调用schedule()选择和执行一个其他进程;
进程通过执行以下几个步骤将自己加入到一个等待队列中:
1)调用宏DEFINE_WAIT()创建一个等待队列的选项。
2)调用add _ wait _ queue()把自己加入队列中。
3)调用prepare _ to _ wait()方法将进程的状态变更为TASK _ INTERRUPTIBLE或TASK _ UNINTERRUPTIBLE(休眠)。
4)如果状态被设置成TASK_INTERRUPTIBLE,则信号唤醒进程。(伪唤醒:唤醒不是因为事件的发生。)
5)当进程被唤醒的时候,会再次检查条件是否为真,真则退出循环,否则再次调用schedule()并且一直重复这步动作。
6当条件满足后,进程将自己设置为TASK _ RUNNING并调用finish _ wait()方法把自己移出等待序列。函数inotify _ read():负责从通知文件描述符中读取信息。
唤醒:进程被设置为可执行状态,然后再从等待队列中移到可执行rbtree中。
4.6 抢占和上下文切换
上下文切换由context _ switch()函数负责,每当一个新进程被选出准备投入运行时,schedule()会调用context _ switch()。
它完成了两项基本工作:
·调用switch_mm(),该函数负责把虚拟内存从上一个进程映射到新进程中。
·调用switch _ to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态。
这包括保存、恢复栈信息和寄存器信息,还有其他任何与体系结构相关的状态信息,都必须以每个进程为对象进行管理和保存。
抢占分为用户抢占和内核抢占。用户抢占都用到need_resched,会导致schedule()被调用,切换到新进程;而内核抢占,受Linux完整的支持,即调度程序没有办法在一个内核级的任务正在执行的时候重新调度。
内核的抢占:
1、为每个进程的thread _ info中引入preempt _ count计数器,初值为0,使用锁+1,释放锁-1,数值为0时,内核可执行抢占。
2、从中断返回内核空间时,先检查need_resched标志,如果被设置表示需要被调度,然后检查preempt_count计数器,如果为0,表示可以被抢占,这时调用调度程序。否则,内核直接从中断返回当前执行进程。
3、当前进程持有的锁全部被释放,这时preempt_count重新为0,释放锁的代码会检查need_resched是否被设置,如果是就调用调度程序。
4、如果内核中的进程被阻塞了,或它显式地调用了schedule(),内核抢占也会显式地发生。
4.7 实时调度策略
Linux提供了两种实时调度策略:SCHED _ FIFO和SCHED _ RR:
SCHED _ FIFO是一种先入先出的调度算法,不使用时间片,该进程一旦处于可执行状态,就会一直执行,直到受阻或显示释放处理器;
SCHED _ RR是带有时间片的实时轮流调度算法,该进程在耗尽实现分配给它的时间后就不再继续执行。
优先级范围
实时:0~[MAX _ RT _ PRIO-1]
默认MAX _ RT _ PRIO=100,所以默认实时优先级范围为[0,99]
SCHED _ NORMAL:[MAX _ RT _ PRIO]~[MAX _ RT _ PRIO+40]。
默认情况下,nice值从-20到+19对应的是从100到139的实时优先级范围。
4.8与调度相关的系统应用
放弃处理器时间:
sched_yield() 让进程显式地将处理器时间让给其他等待执行进程。普通进程移到过期队列中,实时进程移到优先级队列最后。
内核先调用yield,确定给定进程确实处于可执行状态,然后调用sched _ yield()。
用户空间可以直接调用sched _ yield()。
教材第六章 内核数据结构
对于数据结构的知识,之前大学本科没有接触过,结合教材和网上资料,谈谈自己的认识。内核数据结构贯穿于整个内核代码中,利用这4个基本的数据结构,可以在编写内核代码时节约大量时间。
本章主要讲述了数据结构中最有用的四个:
*链表
*队列
*映射
*二叉树
链表
通过对比内核链表和传统链表,可以发现,内核链表更好的解决了传统链表不好共通化的问题:
(1)在传统双向链表中,每个节点(node)除了包含2个指针(pre,next),还包含必要的数据,这些数据存在每个节点中,最大的缺点就是不好共通化;
(2)linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中。
linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独立于用户数据之外,便于实现链表的共同操作。
队列
内核中的队列是以字节形式保存数据的,所以获取数据的时候,需要知道数据的大小。Linux内核通用队列实现成为kfifo,它实现在kernel/kfifo.c中,内核中关于队列定义的头文件位于:<linux/kfifo.h> include/linux/kfifo.h。kfifo提供两个主要操作:enqueue(入队列)和dequeue(出队列),kfifo对象维护了两个偏移量:入口偏移和出口偏移两个偏移量。
内核队列编程需要注意的是:
1、队列在初始化时,size始终设定为2的n次幂
2、使用队列之前将队列结构体中的锁(spinlock)释放
映射
Linux内核提供了简单、有效的映射数据结构,有点想像他语言(C#或者python)中的字典类型,每个唯一的UID对应到一个指针。映射的使用需要注意的是,给自定义的数据结构申请一个id的时候,不能直接申请id,先要分配id(函数idr_pre_get),分配成功后,在获取一个id(函数idr_get_new)。
红黑树
红黑树或许遵循起来比较简单,但一系列规则看起来比较复杂,其插入,删除性能也还不错。红黑树中最长的路径就是红黑交替的路径,最短的路径是全黑节点的路径,再加上根节点和叶子节点都是黑色,
从而可以保证红黑树中最长路径的长度不会超过最短路径的2倍。内核中关于红黑树定义的头文件位于:<linux/rbtree.h> include/linux/rbtree.h
头文件中定义的函数的实现位于:
lib/rbtree.c
之前没有学过数据结构,教材中的代码我不是很看得懂,网上搜到一些资料,对于红黑树的定义和宏定义,有了一定的认识。
参考博客:内核数据结构(红黑树)
总结:
本章学习内核数据结构第一次接触,速度有点慢,尤其红黑树,宏定义这些理解起来比较慢,花费时间比较长,要加强数据结构的学习。
问题与反思:
1、在另一个shell中输入命令
target_remote:1234
为什么会出现连接超时?因为在上一个gdb中,生成的冻结窗口被自己关闭了,要重新-s -S打开 qemu(stopped)冻结窗口,重新输入命令,设置断点,继续执行。
2、TASK_INTERRUPTIBLE接受到一个信号,会被提前唤醒并响应该信号,和TASK_RUNNING的关系区别?
TASK_INTERRUPTIBLE接受信号,提前唤醒但并不执行,其任务是不可行的;而TASK_RUNNING接收到信号,任务是可行的,再调用active_task()把任务加到运行队列。
2017-2018-1 20179205《Linux内核原理与设计》第四周作业的更多相关文章
- 2017-2018-1 20179205《Linux内核原理与设计》第十周作业
<Linux内核原理与设计>第十周作业 教材17.19.20章学习及收获 1.在Linux以及所有unix系统中,设备被分为以下三种:块设备(blkdev)以块为单位寻址,通过块设备节点来 ...
- 2017-2018-1 20179205《Linux内核原理与设计》第九周作业
<Linux内核原理与设计>第九周作业 视频学习及代码分析 一.进程调度时机与进程的切换 不同类型的进程有不同的调度需求,第一种分类:I/O-bound 会频繁的进程I/O,通常会花费很多 ...
- 2017-2018-1 20179205《Linux内核原理与设计》第八周作业
<Linux内核原理与设计>第八周作业 视频学习及操作分析 预处理.编译.链接和目标文件的格式 可执行程序是怎么来的? 以C语言为例,经过编译器预处理.编译成汇编代码.汇编器编译成目标代码 ...
- 2017-2018-1 20179205《Linux内核原理与设计》第七周作业
<Linux内核原理与设计>第七周作业 视频学习及操作分析 创建一个新进程在内核中的执行过程 fork.vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_for ...
- 2017-2018-1 20179205《Linux内核原理与设计》第六周作业
<Linux内核原理与设计> 视频学习及操作 给MenuOS增加time和time-asm命令的方法: 1.更新menu代码到最新版 rm menu -rf //强制删除menu, rm ...
- 2017-2018-1 20179205《Linux内核原理与设计》第五周作业
<Linux内核原理与设计>第五周作业 视频学习及操作分析 一.用户态.内核态和中断 内核态在CPU执行中对应高执行级别,执行级别为0级,具有特权指令,可以访问任意物理地址:用户态执行级别 ...
- 2017-2018-1 《Linux内核原理与设计》第十二周作业
<linux内核原理与设计>第十二周作业 Sql注入基础原理介绍 分组: 和20179215袁琳完成实验 一.实验说明 SQL注入攻击通过构建特殊的输入作为参数传入Web应用程序,而这 ...
- 《Linux内核原理与设计》第十一周作业 ShellShock攻击实验
<Linux内核原理与设计>第十一周作业 ShellShock攻击实验 分组: 和20179215袁琳完成实验及博客攥写 实验内容: Bash中发现了一个严重漏洞shellshock, ...
- 2019-2020-1 20199303<Linux内核原理与分析>第二周作业
2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...
- 2019-2020-1 20199303 《Linux内核原理分析》 第一周作业
2019-2020-1 20199303 <Linux内核原理分析> 第一周作业 1. 环境准备 在众多的Linux发行版中,Ubuntu,小红帽还有类Unix系统的BSD系统,我选择了目 ...
随机推荐
- Python文件操作:同一个文件进行内容替换
在原文件上进行部分内容的替换,主要用到seek()函数和truncate()函数实现,直接上代码: ) # 将指针位置指到文件开头(注意:一定要有这步操作,不然无法清空文件)f1.truncate() ...
- 【bzoj4401】块的计数 结论题
题目描述 给出一棵n个点的树,求有多少个si使得整棵树可以分为n/si个连通块. 输入 第一行一个正整数N,表示这棵树的结点总数,接下来N-1行,每行两个数字X,Y表示编号为X的结点与编号为Y的结点相 ...
- Creator开源游戏、插件、教程、视频汇总
Creator开源游戏.插件.教程.视频汇总 来源 http://forum.cocos.com/t/creator/44782 王哲首席客服 17-03-17 4 史上最全,没有之一. ...
- [六省联考2017]分手是祝愿 期望DP
表示每次看见期望的题就很懵逼... 但是这题感觉还是值得一做,有可借鉴之处 要是下面这段文字格式不一样的话(虽然好像的确不一样,我也不知道为什么,是直接从代码里面复制出来的,因为我一般都是习惯在代码里 ...
- Android <Android应用开发实战> 资源类型<一>
1.字符串资源>>1.普通字符串>>2.字符串数组 <resources> <string-array name="planets_array&qu ...
- (ex)BSGS题表
学了一下BSGS大概知道他是什么了,但是并没有做什么难题,所以也就会个板子.普通的BSGS,我还是比较理解的,然而exBSGS我却只理解个大概,也许还会个板子......(这个东西好像都会有一群恶心的 ...
- 【博弈论】Nim游戏
百度百科 Definition 这样的游戏被称为Nim游戏: 1.有两个玩家,轮流进行操作 2.是公平游戏.即面对同一局面两个玩家所能进行的操作是相同的.例如中国象棋不是公平游戏.因为面对同一个局面, ...
- maven的setting.xml文件中只配置本地仓库路径的方法
maven的setting.xml文件中只配置本地仓库路径的方法 即:settings标签下只有一个 localRepository标签,其他全部注释掉即可 <?xml version=&quo ...
- 背景建模技术(七):预处理(PreProcessor)模块
预处理(PreProcessor)模块是BgsLibrary中一个必选的模块,是真正进入背景建模算法的“预处理”过程,其主要功能包括‘去模糊’.‘获得灰度图’.'应用Canny算子‘等可选模块. 下面 ...
- 利用caffe的solverstate断点训练
你可以从系统 /tmp 文件夹获取,名字是什么 caffe.ubuntu.username.log.INFO.....之类 ====================================== ...