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

《庖丁解牛》第6章——进程的描述和进程的创建

前面两周对系统调用机制进行了仔细的研究,本周将学习用来创建进程的系统调用fork。首先,必须先理解进程如何描述(pcb),其次再理解进程的创建过程和机制。

一、学习内容及试验过程总结

6.1 进程的描述(由于此处的知识会与实验中研究数据结构struct task_struct的任务重复,所以将struct task_struct的知识统一放在实验任务中整理)

此处要主要掌握进程描述符的内容、状态转换和双向链表管理
1.OS内核实现OS的三大功能对应了OS原理课程中最重要的3个抽象概念:

  • 进程管理——进程
  • 内存管理——虚拟内存
  • 文件系统——文件
    其中最核心、最重要的功能就是进程管理
  1. 操作系统原理中使用PCB(Process Control Block,进程控制块)描述进程,也称作进程描述符,它提供了进程相关的所有信息。
    Linux内核中使用 数据结构struct task_struct来描述进程
    3.OS原理中的进程状态和Linux进程的状态的比较
  • OS原理中进程状态有三种:就绪、运行、阻塞
  • Linux内核中的进程状态有:
    • TASK_RUNNING:包括两种状态:进程就绪且没有运行、进程正在运行。这两种状态的区分取决于进程有没有获得CPU的分配权。
    • TASK_ZOMBIE:进程的终止状态,此状态的进程被称作僵尸进程,在此状态下的进程会被Linux内核在适当的时候处理掉,同时进程描述符也将被释放。
    • TASK_INTERRUPTIBLE :可以被信号或者是wake_up()唤醒。信号来临时,进程会被设置为TASK_RUNNING(仅仅是就绪状态而没有执行)
    • TASK_UNINTERRUPTIBLE:只能被wake_up()唤醒

6.2进程的创建

6.2.1 0号进程的初始化

双向链表中的第一个节点为init_task,这是第一个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码方式(宏定义,#difine INIT_TASK(tsk))固定下来的 。除此之外的其他所有进程的初始化都是通过do_fork复制父进程的方式初始化的。

6.2. 2 内存管理相关代码

1.内存管理的相关代码为struct mm_struct *mm,*active_mm;,mm和active_mm是和进程地址空间、内存管理相关的数据结构指针。
2.为了抓住Linux内核中最核心的工作机制,我们不仔细分析物理地址和逻辑地址转换、MMU的具体工作,将进程的地址空间简化为每个进程都有独立的逻辑地址空间。
3.32位x86体系结构拥有4GB的进程地址空间,用户态只能访问0x00000000~0xbfffffff的地址空间,0xc00000000以上的地址空间只能在内核态下访问。内核态可以访问全部4GB的地址空间。(详细可见《庖丁解牛》P68)

6.2.3 进程之间的父子、兄弟关系

在多达400+行的结构体struct task_struct代码中,进程描述符通过struct list_head tasks双向链表管理所有进程(list_head即是双向链表)

  • 当前进程的父进程:通过struct tast_struct __rcu *real_parent,struct task_struct __rcu *parent记录(不是双向链表)
  • 当前进程的子进程:通过struct list_head children记录(是双向链表)
  • 当前进程的兄弟进程:通过struct list_head sibling记录(是双向链表)
    这样设计数据结构是为了方便在内核代码中快速获取当前进程的父子、兄弟进程的信息

6.2.4 保存进程上下文中CPU相关的一些状态信息的数据结构

Linux内核中有与mykernel定义的thread相似的数据结构struct thread_struct,用来保存进程上下文中与CPU相关的一些状态信息,在进程切换是起着很重要的作用。

  • sp:保存进程上下文中的ESP寄存器状态
  • ip:保存进程上下文中的EIP寄存器状态
    此外进程描述符中还有和文件系统相关的数据结构、打开的文件描述符,有和信号处理相关以及和pipe管道相关的数据结构等。进程描述符可以在我们要研究Linux内核的某一部分的特定内容时起到提纲挈领的作用,诶我们进一步深入研究Linux内核提供了基础。

6.2.5 进程的创建过程分析

1.用户态创建进程的方法
2.fork系统调用概览
3.进程创建的主要过程
4.内核堆栈关键信息的初始化
5.通过实验跟踪分析进程创建的过程
fork系统调用过程可以用一个图来在整体上展示系统调用的嵌套

二.实验楼中实验六——分析Linux内核创建一个新进程的过程

1.实验任务

  • 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;
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; //系统打开文件
…
}

do_fork进程


创建一个新进程在整体上可分为两部分,即复制父进程,并完成子进程的初始化
其中,比较关键的有复制父进程描述符(task_struct)dup_task_struct

int copy_thread(unsigned long clone_flags, unsigned long sp,
133	unsigned long arg, struct task_struct *p)
134{
135	struct pt_regs *childregs = task_pt_regs(p);
136	struct task_struct *tsk;
137	int err;
138
139	p->thread.sp = (unsigned long) childregs;
140	p->thread.sp0 = (unsigned long) (childregs+1);
141	memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
142
143	if (unlikely(p->flags & PF_KTHREAD)) {
144		/* kernel thread */
145		memset(childregs, 0, sizeof(struct pt_regs));
146		p->thread.ip = (unsigned long) ret_from_kernel_thread;
147		task_user_gs(p) = __KERNEL_STACK_CANARY;
148		childregs->ds = __USER_DS;
149		childregs->es = __USER_DS;
150		childregs->fs = __KERNEL_PERCPU;
151		childregs->bx = sp;	/* function */
152		childregs->bp = arg;
153		childregs->orig_ax = -1;
154		childregs->cs = __KERNEL_CS | get_kernel_rpl();
155		childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156		p->thread.io_bitmap_ptr = NULL;
157		return 0;
158	}
159	*childregs = *current_pt_regs();
160	childregs->ax = 0;
161	if (sp)
162		childregs->sp = sp;
163
164	p->thread.ip = (unsigned long) ret_from_fork;
165	task_user_gs(p) = get_user_gs(current_pt_regs());
166
167	p->thread.io_bitmap_ptr = NULL;
168	tsk = current;
169	err = -ENOMEM;
170
171	if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
172		p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
173						IO_BITMAP_BYTES, GFP_KERNEL);
174		if (!p->thread.io_bitmap_ptr) {
175			p->thread.io_bitmap_max = 0;
176			return -ENOMEM;
177		}
178		set_tsk_thread_flag(p, TIF_IO_BITMAP);
179	}
180
181	err = 0;
182
183	/*
184	 * Set a new TLS for the child thread?
185	 */
186	if (clone_flags & CLONE_SETTLS)
187		err = do_set_thread_area(p, -1,
188			(struct user_desc __user *)childregs->si, 0);
189
190	if (err && p->thread.io_bitmap_ptr) {
191		kfree(p->thread.io_bitmap_ptr);
192		p->thread.io_bitmap_max = 0;
193	}
194	return err;
195}

子进程内核堆栈关键信息初始化 copy_thread

int copy_thread(unsigned long clone_flags, unsigned long sp,
133	unsigned long arg, struct task_struct *p)
134{
135	struct pt_regs *childregs = task_pt_regs(p);
136	struct task_struct *tsk;
137	int err;
138
139	p->thread.sp = (unsigned long) childregs;
140	p->thread.sp0 = (unsigned long) (childregs+1);
141	memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
142
143	if (unlikely(p->flags & PF_KTHREAD)) {
144		/* kernel thread */
145		memset(childregs, 0, sizeof(struct pt_regs));
146		p->thread.ip = (unsigned long) ret_from_kernel_thread;
147		task_user_gs(p) = __KERNEL_STACK_CANARY;
148		childregs->ds = __USER_DS;
149		childregs->es = __USER_DS;
150		childregs->fs = __KERNEL_PERCPU;
151		childregs->bx = sp;	/* function */
152		childregs->bp = arg;
153		childregs->orig_ax = -1;
154		childregs->cs = __KERNEL_CS | get_kernel_rpl();
155		childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
156		p->thread.io_bitmap_ptr = NULL;
157		return 0;
158	}
159	*childregs = *current_pt_regs();
160	childregs->ax = 0;
161	if (sp)
162		childregs->sp = sp;
163
164	p->thread.ip = (unsigned long) ret_from_fork;
165	task_user_gs(p) = get_user_gs(current_pt_regs());
166
167	p->thread.io_bitmap_ptr = NULL;
168	tsk = current;
169	err = -ENOMEM;
170
171	if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
172		p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
173						IO_BITMAP_BYTES, GFP_KERNEL);
174		if (!p->thread.io_bitmap_ptr) {
175			p->thread.io_bitmap_max = 0;
176			return -ENOMEM;
177		}
178		set_tsk_thread_flag(p, TIF_IO_BITMAP);
179	}
180
181	err = 0;
182
183	/*
184	 * Set a new TLS for the child thread?
185	 */
186	if (clone_flags & CLONE_SETTLS)
187		err = do_set_thread_area(p, -1,
188			(struct user_desc __user *)childregs->si, 0);
189
190	if (err && p->thread.io_bitmap_ptr) {
191		kfree(p->thread.io_bitmap_ptr);
192		p->thread.io_bitmap_max = 0;
193	}
194	return err;
195}
  • 使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。 特别关注新进程是从哪里开始执行的?为什么从那里能顺利执行下去?即执行起点与内核堆栈如何保证一致。

三、学习中遇到的问题

1.markdown格式(.md文件)阅读器for windows

2.调试的中的问题
在一开始打断点的时候,不要把6个断点全部打上跟踪,这样内核在初始化的时候就会在do_fork上被拦住(而不是在sys_clone上)。
所以我们在一开始的时候只要打上sys_clone就好了,这样就先把内核启动起来了,否则会在do_fork断点上出问题,如图

posted @ 2018-11-25 13:39  天青Cris  阅读(245)  评论(0编辑  收藏  举报