`
modabobo
  • 浏览: 506001 次
文章分类
社区版块
存档分类
最新评论

【Linux内核学习笔记】进程的创建过程

 
阅读更多

进程的创建过程

------基于Linux0.11源码分析

1. 背景

进程的创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节。比如,子进程复制父进程所拥有的资源,或者子进程和父进程共享相同的物理页面,拥有自己的地址空间,子进程创建后接受统一调度执行等等。

原理性的书籍更多地关注了进程创建过程中各个关键部分的功能,但由于过于抽象,很难理解,因此如果自己能够实际操作,实践这个过程就很重要,可以让那些看起来抽象的概念变的现实而容易理解,比如所谓的父进程的资源,父进程所拥有的物理页面,甚至父进程的地址空间等等,这些抽象的概念其实只要实际操作一次就更能有感性的认识。本人参考Linux0.11源代码实践了创建进程和调度,这个过程获益匪浅,这里把主要的学习成果结合实践总结一下。

2. 0号进程

子进程的创建是基于父进程的,因此一直追溯上去,总有一个进程是原始的,即没有父进程的。这个进程在Linux中的进程号是0,也就是传说中的0号进程(可惜很多理论书上对这个重要的进程只字不提)。

如果说子进程可以通过规范的创建进程的函数(如:fork())基于父进程复制创建,那么0号进程并没有可以复制和参考的对象,也就是说0号进程拥有的所有信息和资源都是强制设置的,不是复制的,这个过程我称为手工设置,也就是说0号进程是“纯手工打造”,这是操作系统中“最原始”的一个进程,它是一个模子,后面的任何进程都是基于0号进程生成的。

手工打造0号进程最主要包括两个部分:创建进程0运行时所需的所有信息,即填充0号进程,让它充满“血肉”;二是调度0号进程的执行,即让它“动”起来,只有动起来,才是真正意义上的进程,因为进程本身实际上是个动态的概念。

不同的操作系统或者同一个操作系统的不同版本进程信息的内涵可能会有些细微的差距,但大体上关键的部分和逻辑是没有什么不同的,我这里只是基于Linux0.11的实现来描述进程创建的关键步骤和关键细节。

1)填充0号进程信息

进程包括的内容非常复杂,但总的来说进程的信息都是由进程的描述符引导标识的,因此填充0号进程的过程逻辑上是以填充其描述符为牵引完成的(也有书将进程描述符称为进程控制块)。下面是Linux0.11版进程的描述符信息结构体:

struct task_struct {

long state,counter,priority, signal;

struct sigaction sigaction[32];

long blocked;

int exit_code;

unsigned long start_code,end_code,end_data,brk,start_stack;

long pid,father,pgrp,session,leader;

unsigned short uid,euid,suid,gid,egid,sgid;

long alarm;

long utime,stime,cutime,cstime,start_time;

unsigned short used_math;

int tty;

unsigned short umask;

struct m_inode * pwd;

struct m_inode * root;

struct m_inode * executable;

unsigned long close_on_exec;

struct file * filp[NR_OPEN];

struct desc_struct ldt[3];

struct tss_struct tss;

};

可以看到进程描述符里的信息很多,大体上有几部分:

a. 进程的运行信息,如进程的当前状态(state),进程的各种时间片消耗记录(utimestime),进程的信号(signal)和优先级(priority)等。

b. 进程的基本创建信息,如进程号(pid),进程的创建用户(uid)等。

c. 进程的资源类信息,如使用的tty自设备号(tty),文件根目录i节点结构(root)等。

d. 进程执行和切换CPU需要使用的关键信息:局部描述符表(LDT)、任务状态段(TSS)信息。

这些信息并不是在进程创建的时候就全部确定的,大部分只是暂时赋一个初值,在运行的时候会动态更改,也有一些是要在进程运行前设置好的,才能保证进程被正确地执行起来。实际上,我们最需要填充的信息是那些使得操作系统可以顺利切换到0号进程的信息,最重要的显然是进程的LDTTSS信息。TSSCPU在切换任务时需要使用的信息,而LDT是局部描述符表,0号进程是第一个运行在用户态的进程,需要使用自己的LDTTSSLDT是保证不同进程之间相互隔离的重要机制。

实际上还有一个重要的信息不是放在进程本身的描述符里的,而是放在全局描述符表GDT中,因为所有的进程是由操作系统统一管理的,因此操作系统至少要保持对它们的索引,这种索引性质的信息放在操作系统内核的GDT中。对于Linux0.11来说,每个进程都有一个LDT和一个TSS描述符,而Linux2.4之后是每个CPU一个TSS描述符并存储在GDT中,而不是每个进程一个。当然这种区别会造成进程创建和切换过程中一些细节上的差异,但本质的部分和任务的切换过程并没有任何不同。

下面是Linux0.11手动填充进程0的进程描述符信息的宏:

#define INIT_TASK /

/* state etc */ { 0,15,15, /

/* signals */ 0,{{},},0, /

/* ec,brk... */ 0,0,0,0,0,0, /

/* pid etc.. */ 0,-1,0,0,0, /

/* uid etc */ 0,0,0,0,0,0, /

/* alarm */ 0,0,0,0,0,0, /

/* math */ 0, /

/* fs info */ -1,0022,NULL,NULL,NULL,0, /

/* filp */ {NULL,}, /

/* ldt */ { /

{0,0}, /

{0x9f,0xc0fa00}, /

{0x9f,0xc0f200}, /

}, /

/*tss*/ { 0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,/

0,0,0,0,0,0,0,0, /

0,0,0x17,0x17,0x17,0x17,0x17,0x17, /

_LDT(0),0x80000000, {} /

}, /

}

除了填充进程描述符的信息外,还需要在GDT中设置相关的项,即进程0LDTTSS选择符,这个工作是在sched_init()里完成的:

void sched_init(void){

...

set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));

set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));

...

ltr(0);

lldt(0);

}

可以看到,在进程0TSSLDT描述符信息设置到GDT中后,立刻设置了TR寄存器和LDTR寄存器,为即将运行0号进程作准备。

2)运行0号进程

进程0是运行在用户态下的进程,因此就意味着进程0的运行过程实际上是一个从0级特权级到3级特权级切换的过程,使用的是CPU指令iret,模拟了中断调用的返回过程,具体执行过程由move_to_user_mode完成:

#define move_to_user_mode() /

__asm__ ("movl %%esp,%%eax/n/t" /

"pushl $0x17/n/t" /

"pushl %%eax/n/t" /

"pushfl/n/t" /

"pushl $0x0f/n/t" /

"pushl $1f/n/t" /

"iret/n" /

"1:/tmovl $0x17,%%eax/n/t" /

...)

这个宏将进程0执行时的ss,esp,eflags.cs,eip信息全部压栈,待到执行iret指令时,CPU将这几项信息从栈中弹出加载到相应的寄存器中,这样就实现了进程0的启动执行。从这里也可以看出,进程0刚开始执行时几个关键寄存器的信息也是在其运行前事先设定好的,从进程描述符信息到执行信息均是人为设置,因此我称之为“纯手工打造的进程”。

3. 子进程的创建

有了0号进程这个原始的进程,再来看子进程的创建就比较容易理解一些。除了0号进程外,其余的进程均使用系统调用fork()完成,其具体工作由内核态的_sys_fork实现:

_sys_fork:

call _find_empty_process

testl %eax,%eax

js 1f

push %gs

pushl %esi

pushl %edi

pushl %ebp

pushl %eax

call _copy_process

addl $20,%esp

1: ret

可以看到,一个进程的创建主要有两个步骤:一是找到一个空闲进程资源(find_empty_process)Linux0.11来说可以同时运行的进程数目是64个,是有限的,因此需要先得到一个空闲的进程表中的一项用来索引即将创建的进程信息;第二个主要步骤就是复制(copy_process),这个函数具体来实现子进程基于父进程的复制创建。

主要包括的步骤和内容是:

1) 为新进程在内存中分配一个物理页,将新进程的描述符信息填充在该页的开头,并设置新进程的描述符里各项信息;

2) 拷贝父进程的页表,使得它们共同指向相同的物理页,同时将父进程的各个页表属性改为只读,这样将来可以使用写时复制机制。

3) GDT中设置该进程项的TSSLDT选择符。

Linux0.11版本子进程内容的设置主要内容就是这些,当然不同版本会有不同,在改进执行性能上也会有改进,但这个版本体现出来的最基本创建过程基本上反映了操作系统创建进程的主要过程。

4. 子进程的运行

子进程在创建好后并不能立即执行,至少需要一次调度,而这个调度到子进程的运行过程就完全不需要像进程0那样人为在栈上设置信息然后用iret方式,而是执行的任务的切换过程。不考虑进程调度的各个算法和选择细节,最终负责完成切换操作的函数如下:

#define switch_to(n) {/

struct {long a,b;} __tmp; /

__asm__("cmpl %%ecx,_current/n/t" /

"je 1f/n/t" /

"movw %%dx,%1/n/t" /

"xchgl %%ecx,_current/n/t" /

"ljmp %0/n/t" /

"cmpl %%ecx,_last_task_used_math/n/t" /

"jne 1f/n/t" /

"clts/n" /

"1:" /

::"m" (*&__tmp.a),"m" (*&__tmp.b), /

"d" (_TSS(n)),"c" ((long) task[n])); /

}

最终的切换执行了一个ljmp操作,它的操作数是一个任务描述符,这会导致CPU执行一次任务切换,根据新进程的TSS信息将相关信息加载进cs,eip,eflags,ss,esp寄存器开始执行新的代码。当然由于先前拷贝的父进程的相关页面被设置为只读,子进程第一次执行到该页面时会触发页保护的异常,这时会触发写时复制操作,为子进程分配自己的相应页面。

符:任务(task)和进程(process)的区别

任务和进程很容易被人混淆,甚至在Linux中进程描述符结构体也是用task_struct表示,而不是process,这更让人有的时候搞不清楚。我个人认为,其实任务的概念更底层,可以认为是基于CPU的角度来考虑的,进程所处的层次更高一些,应当可以认为是操作系统一级的概念。

任务关注点是一组程序操作,这组操作实现了某个功能,它最终会涉及到指令级别,我们说任务的切换最终需要关注的还是CPU的相关指令。

进程的概念通常是指程序的执行,是动态的过程。进程除了包含其要运行的程序之外,还包括运行时的诸多信息,如运行时间,信号等等。

分享到:
评论

相关推荐

    linux内核笔记——进程管理_80386基础

    初学者在在学习linux内核的时候,其启动代码无疑是横在阅读这恰面的一座大山,才一开始就碰倒了复杂的AT&T语法与保护模式,大多数初学者往往由于无法理解其古怪的语法和难解的数据结构与管理方式,从而放弃在linux...

    Linux内核创建一个进程的过程分析

    不管在什么系统中,所有的任务都是以进程为载体的,所以理解进程的创建对于理解操作系统的原理是非常重要的,本文是我在学习linux内核中所做的笔记,如有错误还请大家批评指正。注:我所阅读的内核版本是0.11。  ...

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

    《Linux内核设计与实现》 第一章 读书笔记 Linux内核简介 面试被怼了Linux内核,于是决定好好看一下这本书。作为经典书籍,Linux内核设计与实现是一本很重要的书籍。在大学本科的课程中已经学习过有关操作系统的内容...

    02_Linux内核分析及编程高清pdf版(2-2)

    本书作者在整理自己多年研发笔记的基础上,以精心挑选的典型开发实例,向读者详细讲述了Linux内核源代码的各部分结构、原理及组成框架,主要分析了 Linux最新版本(2.6.11)的内核源代码,帮助读者深入理解Linux内核...

    Linux系统编程学习笔记

    在这个领域工作的开发者需要深入了解Linux内核和系统底层的工作原理。 Linux 系统编程是指在 Linux 操作系统上进行应用程序和工具开发的一系列活动。这种编程涉及到与 Linux 操作系统核心交互,以实现各种功能,如...

    linux学习笔记(包含Linux系统和shell编程).rar

    一、初识 Linux shell Linux 系统可划分为以下 4 部分。 Linux 内核 GNU 工具 ...内核创建了第一个进程(称为 init 进程)来启动系统中所有其他进程。当内核启动时,它会将 init 进程载入虚拟内存。

    linux-2.6.24:linux kernel 2.6.24原始代码分析和学习笔记

    Linux 2.6内核学习笔记本仓库已经开始作为gitbook仓库,访问地址 GitHub访问地址 Something I hope you know before go into the coding~First, please watch or star this repo, I'll be more happy if you follow ...

    Linux内核调试技术——进程D状态死锁检测-List_linux-亚虎娱乐博客 - 亚虎娱乐1

    个人简介围城文章分类全部亚虎娱乐(123)学习笔记(13)职场&人生(2)杂谈(0)编译&调试(12)IT 基础(24)C 基础(5)未分配的亚虎娱乐(0)文章

    Linux读书笔记;推荐书籍.rar

    如果你想要成为一名合格的Linux管理员linux学习书籍,那么必须要学习一些基础知识和技能。而最好的学习方法就是通过阅读专业书籍来掌握这些技能。下面是几本值得推荐的Linux学习书籍,它们...3.《深入理解Linux内核》

    linux下如何创建守护进程的步骤

    这两天学习了linux 守护进程这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记。 1,进程的概念:程序的一次动态执行过程。  进程存在于内存当中,存在着 创建,调度,执行和消亡,进程号是进程的唯一...

    linux的进程线程编程(高清书签版)

    linux进程线程编程的学习笔记整理!继《linux的IO编程》的第二个大的部分——进程和线程的编程,后序还有《linux的网络编程》正在整理中……

    Linux-Cpp-Development-Advanced-Learning::smirking_face:Linux Cpp 后台开发进阶学习,校招必学:操作系统、计算机网络、网络编程、并发实战、数据库原理、设计模式、Linux内核笔记、Linux命令和Git等。校招笔记、项目,你值得拥有

      本项目用于Linux Cpp后台开发秋招学习,内容主要涵盖以下几个部分:Cpp进阶,操作系统, 计算机网络, Linux内核,MySQL数据库, Redis数据库, 数据结构与算法,Leetcode刷题等内容。我会对校招所需掌握的基础...

    《深入理解LINUX内存管理》学习笔记

    作此笔记备忘2,一直以来学习LINUXkernel的知识缺乏系统化,借对这本书的学习,系统化的学习一下LINUX kernel。3,自己一直在做一个toosmall,toosimple的单进程,特权模式,64bit保护模式的称不上OS的OS,已经做...

    Linux协议栈阅读笔记

    在微内核中,大部分内核都作为独立的进程在特权状态下运行,他们通过消息传递进行通信。在典型的情况下,每个模块都有一个进程。在这种设计中,微内通常作为消息转发站的角色。其基本思想是保持微内核尽量小,这样...

    深度探索Linux操作系统:系统构建和原理解析.王柏生(带详细书签).part2.rar

    它颠覆和摒弃了传统的从阅读Linux内核源代码着手学习Linux操作系统原理的方式,而是基于实践,以从零开始构建一个完整的Linux操作系统的过程为依托,指引读者在实践中去探索操作系统的本质。这种方式的妙处在于,让...

    Linux-文件系统-学习笔记(14):根文件系统原理与nfs搭建方法

    Linux-文件系统-学习笔记(14):根文件系统原理与nfs搭建方法 一、根文件系统 1、根文件系统简介 为什么需要根文件系统? (1)init进程的应用程序在根文件系统上,因此向用户态切换时根文件系统必不可少。 (2)根...

    嵌入式学习笔记

    这个嵌入式学习笔记记录了本人多年来从事嵌入式linux+arm开发所遇到的重点难点,也包含了各大嵌入式公司招聘时最喜欢问到的知识点,特别适用于马上就要找工作的同学,也可作为嵌入式开发人员进行项目开发时的实用...

    linux管理员指南

    而技巧性很强的编译某个Linux内核的任务将在第10章进行详细的介绍,这一章的内容不仅介绍整个过程的每一个步骤,还对每一步做了解释。在第二部分的结尾,我们用了一章的篇幅介绍基本的系统安全措施。 ?第三部分 在第...

    BigData::gem_stone::fire:大数据学习笔记

    大巴塔 Hbase是数据库,Hive是数据仓库 MapReduce执行过程 ... 进程是正在执行的程序实例,执行程序时,内核会将程序代码首先加载到虚拟内存,为程序变量分配内存空间,并为进程建立 记账数据结构,

Global site tag (gtag.js) - Google Analytics