张雨梅   原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000

创建新进程

如果同一个程序被多个用户同时运行,那么这个程序就有多个相对独立的进程,与此同时他们又共享相同的执行代码。在Linux系统中进程的概念类似于任务或者线程

创建进程,调用fork函数,这是一个系统函数。fork函数与系统函数的调用大体相同,但是fork之后产生了一个新的进程,会发生两次返回。

task_struct的数据结构

每个可以独立被调度的执行上下文都有一个进程描述符,不管是进程还是线程都有自己的 task_struct结构

task_struct的定义链接是http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#task_struct,其代码很长,下面就列出一部分

struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped 描述进程运行状态*/
void *stack; //指定进程的内核堆栈
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace; #ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
struct task_struct *last_wakee;
unsigned long wakee_flips;
unsigned long wakee_flip_decay_ts; int wake_cpu;
#endif
int on_rq; //运行队列 int prio, static_prio, normal_prio; //定义优先级
unsigned int rt_priority;
const struct sched_class *sched_class; //进程调度相关的定义
struct sched_entity se;
struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
struct task_group *sched_task_group;
#endif
struct sched_dl_entity dl;
......

linux进程的状态有

task_running,就绪态、运行态都用这个表示,这与操作系统中的定义不同,这种定义方式表示进程是可运行的,就绪状态不同的是要等待cpu的调度。

task_zombie,僵尸状态,即死锁

task_interruptible或task_uninterruptible,阻塞状态

还有_task_stopped,_task_traced,exit_zombie,exit_dead等各种状态。

PCB中指定了内核栈,没有指定用户栈,这是因为进程由用户态进入内核态时,保存了用户态进程的现场,恢复现场时,即可找到用户栈。

struct list_head tasks;//进程链表,链接所有当前的进程
#ifdef CONFIG_SMP
struct plist_node pushable_tasks;
struct rb_node pushable_dl_tasks;
#endif

其中list_head是一个双向链表,表示当前进程的前驱和后继关系,其定义是

struct list_head{
struct list_head *next,*prev;
};
struct mm_struct *mm, *active_mm;

这是进程的地址空间管理,与数据段、代码段、分页、分段、物理空间用户逻辑空间的转换有关,这里忽略掉这些细节。Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

    pid_t pid;
pid_t tgid;

pid,process id

tgid,thread group id

linux给每一个进程和轻量级进程都分配一个pid,程序员希望向进程发送信号时,此信号可以影响进程及进程产生的轻量级进程,因此产生了线程组(可以理解为轻量级进程组)的概念,在线程组内,每个线程都使用此线程组内第一个线程的pid,并将此值存入tgid。也就是说一个组内的线程的tgid相同。

#ifdef CONFIG_CC_STACKPROTECTOR
/* Canary value for the -fstack-protector gcc feature */
unsigned long stack_canary;
#endif

stack-canary是设置的栈警卫,可以用来保护栈防止被攻击,它所在的地址如果被改写,就认为栈受到了攻击。

struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
/*
1345 * children/sibling forms the list of my natural children
1346 */
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct task_struct *group_leader; /* threadgroup leader */
......

这一段定义了进程的父子关系,组等,list-head是进程链表,含有前驱、后继的设置,用来表示进程的父子关系,ptraced做调试使用, 前面介绍的tgid的值就是*group_leader的进程号。

cputime_t utime, stime, utimescaled, stimescaled;
cputime_t gtime;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
struct cputime prev_cputime;
#endif
......//和时间相关的一些定义
/* CPU-specific state of this task */
struct thread_struct thread;

这个是和cpu相关的状态,其中包括sp、ip、es、fs、gs、error_code等。是一个比较重要的数据结构。其他定义这里先不分析。

fork函数对应的内核处理过程sys_clone

fork函数用于创建子进程,创建的进程与当前的进程是一个复制的过程,但是中间也会有修改,比如进程的id等。创建子进程之后,子进程与父进程可以说是独立的,可以各自运行。创建进程要发生系统调用,调用system_call,执行相应的系统函数,这里就是sys_clone。fork()调用执行一次返回两个值,对于父进程,fork函数返回子程序的进程号,而对于子程序,fork函数则返回零,这就是fork函数的一次调用,两次返回。

 int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < )
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-);
}
else if (pid == )
{
/* 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");
}
}

比如上面的程序,pid=0,表示子进程,pid>0表示父进程,在子进程中,执行printf("This is Child Process!\n");在父进程中,执行printf("This is Parent Process!\n");也就是说这里的if,else两种情况都会执行到。

fork创建的子进程,从用户态的角度看,是从fork的下一句命令执行。在内核中,fork是一个新创建的进程,从my_process开始执行。

linux中进程创建有三种方法fork,vfork,clone。

fork,子进程只是完全复制父进程的资源,子进程有自己的task_struct结构和pid, 数据空间,堆和栈的副本都是复制父进程的。

vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响到父进程。用 vfork创建子进程后,父进程会被阻塞直到子进程调用exec(exec,将一个新的可执行文件载入到地址空间并执行之)或exit。vfork的好处是在子进程被创建后往往仅仅是为了调用exec执行另一个程序,因为它就不会对父进程的地址空间有任何引用,所以对地址空间的复制是多余的 ,因此通过vfork共享内存可以减少不必要的开销。

clone()带有参数,fork()和vfork()是无参数的,fork()是全部复制,vfork()是共享内存,clone()是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags来决定。另外,clone()返回的是子进程的pid。

SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return do_fork(SIGCHLD, , , NULL, NULL);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, ,
, NULL, NULL);
}
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#endif
{
return do_fork(clone_flags, newsp, , parent_tidptr, child_tidptr);
}
#endif

可以看到fork,vfork,clone函数都会调用do_fork函数。clone函数有些参数,linux定义了多种参数不同的clone函数。下面看一下do_fork的代码。

long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = ;
long nr;
......
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace);

do_fork实现进程的创建,调用copy_process,

static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid, int trace)
{
int retval;
struct task_struct *p; if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))//出错处理
return ERR_PTR(-EINVAL);
......
retval = security_task_create(clone_flags);
if (retval)
goto fork_out; retval = -ENOMEM;
p = dup_task_struct(current);//复制进程的pcb
if (!p)
goto fork_out; ftrace_graph_init_task(p); rt_mutex_init_task(p);
1247
......
    p->utime = p->stime = p->gtime = 0;//修改子进程的参数,也是初始化
p->utimescaled = p->stimescaled = 0;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
p->prev_cputime.utime = p->prev_cputime.stime = 0;
#endif
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
seqlock_init(&p->vtime_seqlock);
p->vtime_snap = 0;
p->vtime_snap_whence = VTIME_SLEEPING;
#endif

copy_process是创建进程的主要代码。前面定义了一些出错处理实现进程的复制,同时修改子进程中的一些信息,pid等。其中有copy_thread,包含thread的sp、ip等。然后调用dup_task_struct函数。

305static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;//
struct thread_info *ti;
int node = tsk_fork_get_node(orig);
int err; tsk = alloc_task_struct_node(node);//分配一个结点空间
if (!tsk)
return NULL; ti = alloc_thread_info_node(tsk, node);//分配一个进程内核堆栈空间,创建了一个页面,一部分存放thread_info,一部分存放内核堆栈(从高地址向低地址)
if (!ti)
goto free_tsk; err = arch_dup_task_struct(tsk, orig);//复制orig进程的信息给当前进程
if (err)
goto free_ti;

复制进程的时候会调用arch_dup_task_struct,功能是把一个指针赋给另一个指针,实现的股票功能就是把orig赋给tsk。

int __weak arch_dup_task_struct(struct task_struct *dst,
struct task_struct *src)
{
*dst = *src;
return ;
}

然后执行dup_task_struct函数,复制父进程的内核堆栈,copy_thread函数,拷贝寄存器信息,esp,eip等,然后到ret_from_fork,是子进程在内核中执行的起点。

首先修改实验楼的menuos操作系统,添加fork系统函数。在实验楼的虚拟机上进行实验,在终端输入命令,可以看到menuo已经包含fork指令。

用gdb调试观察fork的执行过程,在终端输入命令:

qemu  -kernel  linux3.18.6/arch/x86/boot/bzImage  -initrd  rootfs.img  -s -S

重新打开一个终端,用gdb调试

gdb

file linux-3.18.6/vmlinux

target remote:1234

设置断点为:sys_clone, do _fork, copy_process, dup_task_struct, copy_thread, ret_from_fork

在本次实验的menuos环境中,fork, vfork, clone调用的是sys_clone,不是sys_fork, 然后都会调用do_fork.

观察到fork一个子进程时,其执行过程是

总结

进程控制块PCB,包含了进程中的各种信息,pid,状态,内核栈,进程链表,调度相关的定义,内存管理,文件系统等,内容很强大很复杂。fork产生一个子进程,与父进程的情况大概相同,之后子进程与父进程是相互独立的。在父进程中的返回和一般系统调用一样,在子进程返回到ret_from_fork。

参考资料:http://blog.sina.com.cn/s/blog_7673d4a5010103x7.html  fork,vfork,clone

Linux内核创建一个新进程的更多相关文章

  1. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

  2. 第六周分析Linux内核创建一个新进程的过程

    潘恒 原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 task_struct结构: ...

  3. 实验 六:分析linux内核创建一个新进程的过程

    实验六:分析Linux内核创建一个新进程的过程 作者:王朝宪  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029 ...

  4. 20135202闫佳歆--week6 分析Linux内核创建一个新进程的过程——实验及总结

    week 6 实验:分析Linux内核创建一个新进程的过程 1.使用gdb跟踪创建新进程的过程 准备工作: rm menu -rf git clone https://github.com/mengn ...

  5. 《Linux内核--分析Linux内核创建一个新进程的过程 》 20135311傅冬菁

    20135311傅冬菁 分析Linux内核创建一个新进程的过程 一.学习内容 进程控制块——PCB  task_struct数据结构 PCB task_struct中包含: 进程状态.进程打开的文件. ...

  6. 作业六:分析Linux内核创建一个新进程的过程

    分析Linux内核创建一个新进程的过程 进程描述符PCB----task_struct数据结构 操作系统:1.进程管理 2.内存管理 3 文件系统 一.新进程如何创建和修改task_struct数据结 ...

  7. Linux内核分析-分析Linux内核创建一个新进程的过程

    作者:江军 ID:fuchen1994 实验题目:分析Linux内核创建一个新进程的过程 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/li ...

  8. Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程

    Linux内核分析第六周学习笔记--分析Linux内核创建一个新进程的过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  9. 第六周——分析Linux内核创建一个新进程的过程

    "万子恵 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 &q ...

  10. 实验六:分析Linux内核创建一个新进程的过程

    原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 题目自拟,内容围绕对Linu ...

随机推荐

  1. UEFI引导在GPT分区下安装win2008——抓住那只傲娇的win2008

    上周遇到个客户DELL R520的服务器新采购了8块3T硬盘做备份服务器,raid配置5+1,一个磁21.8T.先用普通的装desktop OS的方法发现进去没raid盘,然后就按照官方的文档进入Li ...

  2. eclipse tomcat 集成

    1. 下载 Tomcat        作者选择的是 Tomcat6,下载地址:http://tomcat.apache.org/download-60.cgi,选择绿色版的 zip 进行下载(目前最 ...

  3. The connection to adb is down,and a server error has occured.解决办法---------------------亲测有效

    认真读error: 办法一: 点击项目右键->Android tools ->Fix Project Properties,检查项目属性 办法二: 设备管理器,查看是否存在adb进程 如果 ...

  4. Linux系统安装-系统分区

    ctrl+alt+enter全屏

  5. C#中try catch中throw ex和throw方式抛出异常有何不同

    我们在C#的try catch代码块中里面经常使用throw语句抛出捕捉到的异常,但是你知道吗使用throw ex和throw抛出捕获到的异常效果是不一样的. 异常捕捉的原理 首先先介绍一下C#异常捕 ...

  6. 使用java mail 发送邮件

    1.关联jar包:   activation.jar   mail.jar 2.调用 @Test public void test1() { List<String> imageUrlLi ...

  7. Base:-用数组赋值实现while和shift功能

    3列数组,arrayA,arrayB,arrayC:分别一一对应主机及其主机所能创建的资源数,还有arrayC表示需要创建的资源数:arrayA=("192.168.1.1" &q ...

  8. 20145227&20145201 《信息安全系统设计基础》实验四

    北京电子科技学院(BESTI) 实 验 报 告 课程:信息安全系统设计基础 班级:1452 姓名:(按贡献大小排名)鄢曼君 李子璇 学号:(按贡献大小排名)20145227 20145201 成绩: ...

  9. 逗比的wifi开关

    笔记本会出现网卡开机不能用的现象.具体表现为:网卡没有禁用,但是搜索不到无线信号.适配器选项框里面选中无线网卡,然后诊断这个链接提示启用无线功能.然后我点击应用此修复就能搜索到无线信号了.问题是,电脑 ...

  10. 笔记汇总.md

    ```javascript 1.js对象,value的值取不到,试着将引入的js文件放到body的后面 $("input[type='time']").change( functi ...