代码改变世界

2017-2018-1 20179226《Linux内核原理与分析》第七周作业

2017-11-12 19:13  20179226任逸飞  阅读(199)  评论(0编辑  收藏  举报

视频课程

task_struct的数据结构

struct task_struct { 
 volatile long state;        //进程状态/* -1 unrunnable, 0 runnable, >0 stopped */
 void *stack;                // 指定进程内核堆栈
 pid_t pid;                  //进程标识符
 unsigned int rt_priority;   //实时优先级
 unsigned int policy;        //调度策略
 struct files_struct *files; //系统打开文件
 ...
}

fork系统调用

fork系统调用会创建一个当前进程的子进程。C语言库函数中的fork()在父进程中的返回值为子进程的pid,在子进程中的返回值为0。我们可以根据返回值的不同令父进程和子进程分别执行各自的任务。

fork系统调用原理

fork系统调用与其它系统调用相似,都要利用int 0x80指令产生中断,然后由操作系统进行关闭中断和保护现场的工作,通过查询系统调用表找到fork系统调用的入口地址。这个入口一直一般为sys_clone, sys_fork, sys_vfork中的一个,这三个入口最终都会调用do_fork()函数。C库函数中的fork()函数会调用sys_clone。
do_fork()函数中会调用copy_process()函数,其中会调用dup_task_struct()函数,该函数为新创建的子进程分配新的PCB和新的内存空间,并将父进程内存空间中的内容复制到子进程的内存空间中。这些内容包括保护现场时寄存器的内容。
接下来,在copy_process()函数中会为子进程进行初始化,包括父进程打开的文件、文件系统、内存空间等等。接下来会调用copy_thread()函数,设置子进程的sp指针和ip指针,同时将子进程的ax寄存器的值置为0。这就是为什么子进程的fork调用会返回0。需要注意的是,子进程的ip指针被设置为ret_from_fork,即当子进程开始执行时,会从ret_from_fork启动。
ret_from_fork会跳转到syscall_exit执行。回顾上周的内容,syscall_exit是系统调用结束后执行的位置。即子进程开始执行时,系统调用已经结束,此时子进程的状态相当于系统调用开始前、保护现场结束后的状态,接下来子进程还会经理恢复现场、重新调度等过程,最终执行iret并返回用户态。

实验楼实验

使用gdb跟踪分析一个fork系统调用内核处理函数

1.首先在MenuOS中添加fork命令,命令如下:

#include <unistd.h> 
int Fork(int argc, char *argv[]) 
{
int pid;
/* fork another process */
pid = fork();                                 
if (pid<0) 
{ 
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
} 
else if (pid==0)                              
{
/*   child process  */
printf("This is Child Process!\n");
} 
else 
{   
/*  parent process   */
printf("This is Parent Process!\n");          
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}

在main函数中加入如下命令MenuConfig("fork","Fork a new process",Fork);,保存退出

2.在menu目录下执行命令make rootfs,编译结果如下图所示:

3.接下来要使用gdb来跟踪调试进程创建过程,所以需要设置断点。


当按c继续执行之后,后面的断点依次如图所示:
停在的do_fork()的位置上

继续单步执行就看到了copy_process

继续单步执行进入dup_task_struct

继续单步执行到copy_thread

遇到的问题

在gdb中file linux-3.18.6/vmlinux加载内核的时候,没有这个文件,后来发现不能在menu目录下gdb,应该在LinuxKernel目录下gdb

阅读教材第11、12章

1.系统定时器以某种频率自行触发或射中时钟中断,该频率可以通过变成预定,称作节拍率。节拍率是通过静态预处理定义的,也就是HZ,在系统启动时按照HZ值对硬件进行设置。大多数体系结构的节拍率都是可调的。
2.提高节拍率等同于提高中断解析度,意味着有以下好处:
1)更高的时钟中断解析度可提高事件驱动事件的解析度。
2)提高了事件驱动事件的准确度。
3.高HZ的优势
1)内核定时器能够以更高的频度和更高的准确度运行。
2)依赖定时值执行的系统调用。
3)对诸如资源消耗和系统运行时间等的测量会有更精细的解析度。
4)提高进程抢占的准确度。
4.高HZ的劣势
1)系统负担重。
2)中断处理程序占用的处理器的时间多。
3)频繁打乱处理器高速缓存并增加耗电。
5.实时时钟是用来持久存放系统时间的设备,即使系统关闭后,它也可以靠主板上的微型电池提供的电力保持系统的计时。
6.时钟中断处理程序可以划分为两个部分:体系结构相关部分和体系结构无关部分。
7.定时器是管理内核流逝的时间的基础,内核定时器能够使工作在指定时间点上执行。
8.内核代码除了使用定时器或下半部机制以外,还需要其他方法来推迟执行任务,最简单的延迟方法是忙等待,有时内核代码不但需要很短暂的延迟,而且还要求延时的时间很精确。
9.内核把物理页作为内存管理的基本单位,内存管理单元(MMU)以页大小为单位来管理系统中的页表,flag域用来存放页的状态,_count域存放页的引用计数,vitual域是页的虚拟地址。
10.Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
1)一些硬件只能用某些特定的内存地址来执行DMA。
2)一些体系结构的内存的物理寻址范围比虚拟寻址范围大得多。这样,就有一些内存不能永久地映射到内核空间上。
11.Linux主要使用了四种区:ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGHEM。Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配了。
12.vmalloc()函数的工作方式类似于kmalloc(),只不过前者分配的内存虚拟地址是连续的,而物理地址则无需连接。
13.在高端内存中的页不能永久地映射到内核地址空间上。允许永久映射的数量是有限的,当不再需要高端内存时,应该解除映射。当必须创建一个映射而当前的上下文又不能睡眠时,内核提供了临时映射。
14.使用每个CPU数据的原因:首先是减少了数据锁定,第二个好处是可以大大减少缓存失效。使用每个CPU数据会省去许多数据上锁,它唯一的安全要求就是要禁止内核抢占。
15.分配函数的选择:如果你想从高端内存进行分配,就使用alloc_pages();如果你不需要物理上连续的页,仅仅需要虚拟地址上连续的页,那么就使用vmalloc();如果你要创建和撤销很多大的数据结构,那么考虑建立slab高速缓存。