一、知识概要
进程的描述
进程描述符task_struct数据结构(一)
进程描述符task_struct数据结构(二)
进程的创建
进程的创建概览及fork一个进程的用户态代码
理解进程创建过程复杂代码的方法
浏览进程创建过程相关的关键代码
创建的新进程是从哪里开始执行的?
使用gdb跟踪创建新进程的过程
二、学习笔记
进程的描述
1.进程描述符task_struct数据结构(一)
为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。
struct task_struct数据结构很庞大
Linux进程的状态与操作系统原理中的描述的进程状态似乎有所不同,比如就绪状态和运行状态都是TASK_RUNNING,为什么呢?
进程的标示pid
所有进程链表struct list_head tasks; 内核的双向循环链表的实现方法 - 一个更简略的双向循环链表
程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系
Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈
进程处于内核态时使用,不同于用户态堆栈,即PCB中指定了内核栈,那为什么PCB中没有用户态堆栈?用户态堆栈是怎么设定的?
内核控制路径所用的堆栈很少,因此对栈和Thread_info来说,8KB足够了
struct thread_struct thread; //CPU-specific state of this task
文件系统和文件描述符
内存管理——进程的地址空间
Linux内核状态转换图:
2.进程描述符task_struct数据结构(二)
双向循环链表图如下:
进程的父子关系直观图:
进程的创建
1.进程的创建概览及fork一个进程的用户态代码
(1)进程的起源再回顾
道生一(start_kernel...cpu_idle)
一生二(kernel_init和kthreadd)
二生三(即前面的0、1、2三个进程)
三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
(2)0号进程手工写,1号进程复制、加载init程序
(3)shell命令行是如何启动进程的
fork一个子进程的代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 int main(int argc, char * argv[])
5 {
6 int pid;
7 /* fork another process */
8 pid = fork();
9 if (pid < 0) 出错处理
10 {
11 /* error occurred */
12 fprintf(stderr,"Fork Failed!");
13 exit(-1);
14 }
15 else if (pid == 0)
16 {
17 /* child process */ 子进程 pid=0时 if和else都会执行 fork系统调用在父进程和子进程各返回一次
18 printf("This is Child Process!\n");
19 }
20 else
21 {
22 /* parent process */
23 printf("This is Parent Process!\n");
24 /* parent will wait for the child to complete*/
25 wait(NULL);
26 printf("Child Complete!\n");
27 }
28 }
2.理解进程创建过程复杂代码的方法
(1)系统调用再回顾
(2)fork的子进程是从哪里开始执行的?
与基于mykernel写的精简内核对照起来。
(3)创建一个新进程在内核中的执行过程
fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
复制一个PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
要给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process
1 *childregs = *current_pt_regs(); //复制内核堆栈
2 childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3
4 p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5 p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
(4)理解复杂事物要预设一个大致的框架。
(5)创建新进程是通过复制当前进程来实现的。
(6)设想创建新进程过程中需要做哪些事
3.浏览进程创建过程相关的关键代码
(1)系统调用内核处理函数sys_fork、sys_clone、sys_vfork
最终都是执行do_fork()。
do_fork()里的复制进程的函数:
具体:
打开复制PCB的具体函数:
打开alloc_thread_info():
拷贝内核堆栈数据和指定新进程的第一条指令地址。
4.创建的新进程是从哪里开始执行的?
(1)复制内核堆栈时
打开pt_regs:
int指令和SAVE_ALL压到内核栈的内容。
下面分析entry_32.S,也就是总控程序。
5.使用gdb跟踪创建新进程的过程(见作业)
三、课后作业
1.理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;
具体代码分析过程见第二部分学习过程 进程的描述 部分——2.进程描述符task_struct数据结构(二)
2.分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;
具体代码分析过程见第二部分学习过程 进程的创建 部分——3.浏览进程创建过程相关的关键代码(1)系统调用内核处理函数sys_fork、sys_clone、sys_vfork
理解如下:
fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建,do_fork完成了创建中的大部分工作,该函数调用copy_process()函数,然后让进程开始运行。copy_process()函数工作如下:
1、调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同
2、检查
3、子进程着手使自己与父进程区别开来。进程描述符内的许多成员被清0或设为初始值。
4、子进程状态被设为TASK_UNINTERRUPTIBLE,以保证它不会投入运行
5、copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置
6、调用alloc_pid()为新进程分配一个有效的PID
7、根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等
8、最后,copy_process()做扫尾工作并返回一个指向子进程的指针
3.使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。
分析过程如下:
更新menu代码到最新版、make rootfs,用help查看,新添加fork命令:
使用gdb跟踪调试内核,在一些重要函数处设置断点:
执行一个fork,会发现只输出一个fork的命令描述,后面并没有执行,因为它停在了sys_clone这个位置。
4.特别关注新进程是从哪里开始执行的?为什么从哪里能顺利执行下去?即执行起点与内核堆栈如何保证一致。
答:ret_from_fork;决定了新进程的第一条指令地址。
原因如下:
copy_process()主要完成进程数据结构,各种资源的初始化。
p = dup_task_struct(current);
(省略的IF语句)检查clone_flags参数,防止无效的组合进入
p = dup_task_struct(current);调用dup_task_struct()为新进程创建一个内核栈
判断权限及允许范围的代码
对子进程的描述符初始化和复制父进程的资源给子进程
retval = sched_fork(clone_flags, p);完成调度相关的设置,将这个task分配给CPU
if (retval)语句群,复制共享进程的的各个部分
retval = copy_thread(clone_flags, stack_start, stack_size, p);复制父进程堆栈的内容到子进程的堆栈中去.这其中,copy_thread()函数中的语句p->thread.ip = (unsigned long) ret_from_fork;决定了新进程的第一条指令地址.
总结原因也就是说:
在ret_from_fork之前,也就是在copy_thread()函数中*childregs = *current_pt_regs();该句将父进程的regs参数赋值到子进程的内核堆栈,
*childregs的类型为pt_regs,里面存放了SAVE ALL中压入栈的参数
故在之后的RESTORE ALL中能顺利执行下去.
5.根据本周所学知识分析fork函数对应的系统调用处理过程,撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,
总结:
Linux通过复制父进程来创建一个新进程,通过调用do_fork来实现
Linux为每个新创建的进程动态地分配一个task_struct结构.
为了把内核中的所有进程组织起来,Linux提供了几种组织方式,其中哈希表和双向循环链表方式是针对系统中的所有进程(包括内核线程),而运行队列和等待队列是把处于同一状态的进程组织起来
fork()函数被调用一次,但返回两次
linux第六周的更多相关文章
-
Linux第六周学习总结——进程额管理和进程的创建
Linux第六周学习总结--进程额管理和进程的创建 作者:刘浩晨 [原创作品转载请注明出处] <Linux内核分析>MOOC课程http://mooc.study.163.com/cour ...
-
20135337朱荟潼 Linux第六周学习总结——进程的描述和进程的创建
朱荟潼 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课http://mooc.study.163.com/course/USTC 1000029000 第六周 进程的描述 ...
-
20135320赵瀚青LINUX第六周学习笔记
赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 概述 这周主要讲解的是进程. ...
-
Linux 第六周实验
姬梦馨 原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程控制块PCB——task_st ...
-
20135316王剑桥 linux第六周课实验笔记
6.存储器层次结构 6.1存储技术 1.如果你的程序需要的数据是存储在CPU寄存器中的,那么在执行期间,在零个周期内就能访问到它们.如果存储在高速缓冲中,需要1-10个周期.如果存储在主存中,需要50 ...
-
Linux第六周作业
一 实验过程 1 先进入LinuxKernel环境下,更新menu代码到最新版,用到的命令为rm menu -rf //强制删除当前menu,git clone http://git.shiyanlo ...
-
Linux内核设计第六周 ——进程的描述和创建
Linux内核设计第六周 ——进程的描述和创建 第一部分 知识点总结 一.进程描述符task_struct数据结构 1.操作系统的三大功能: 进程管理.内存管理.文件系统 2.进程的作用: 将信号.进 ...
-
《Linux内核分析》第六周学习总结
<Linux内核分析>第六周学习总结 ——进程的描述和进程的创建 姓名:王玮怡 学号:20135116 一.理论部分 (一)进程的描述 1 ...
-
《Linux内核分析》第六周学习笔记
<Linux内核分析>第六周学习笔记 进程的描述和创建 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/co ...
随机推荐
-
Codeforces Round #385(div 2)
A =w= B QwQ C 题意:n个点m条边的无向图,其中有k个特殊点,你在这张图上尽可能多的连边,要求k个特殊点两两不连通,问最多能连多少边 分析:并查集 对原图做一次并查集,找出特殊点所在集合中 ...
-
Repository 仓储,你的归宿究竟在哪?(二)-这样的应用层代码,你能接受吗?
写在前面 关于"Repository 仓储,你的归宿究竟在哪?"这个系列,本来是想写个上下篇,但是现在觉得,很有多东西需要明确,我也不知道接下来会写多少篇,所以上一篇的标题就改成了 ...
-
HDU-4089 Activation
http://acm.hdu.edu.cn/showproblem.php?pid=4089 Activation Time Limit: 20000/10000 MS (Java/Others) ...
-
汇编学习笔记(14)BIOS对键盘输入的处理
字符的处理 键盘输入的字符一般由int9中断例程从60h端口中读取,并存放在键盘缓冲区中,由int16h例程从键盘缓冲区中读取相应字符,CPU对键盘输入a.shift_a的处理过程如下 1.一开始没有 ...
-
cn_office2016
office 2016是可用激活器激活的,但是激活office 365就有点困难了. 附上office 2016下载地址:http://pan.baidu.com/s/1pLTqPyr 破解机:htt ...
-
详解EBS接口开发之采购申请导入
更多内容可以参考我的博客 详解EBS接口开发之采购订单导入 http://blog.csdn.net/cai_xingyun/article/details/17114697 /*+++++++ ...
-
转 Using Async for File Access
原文:https://msdn.microsoft.com/en-us/library/jj155757.aspx using System; using System.Collections.Gen ...
-
oracle 结果集合并
SELECT case ' then ()) FROM T_UEP_DB_UNION A ' ' start with A.UNION_CODE = U.UNION_CODE CONNECT BY P ...
-
Keil中查看.c和.h文件的路径
方法一: 选择任意一个文件,然后点击右键,选择"Option for File xxx"即可查看该文件的路径. 方法二: 单击任意一个文件,然后点击右键,选择"Open ...
-
js 字符串操作方法
1.字符串转换 你可以将任何类型的数据都转换为字符串,你可以用下面三种方法的任何一种: 1 2 var num= 19; // 19 var myStr = num.toString(); // &q ...