do_fork源码阅读

来源:互联网 发布:网络课程直播的好处 编辑:程序博客网 时间:2024/06/05 19:58

fork、vfork和clone

进程是动态的程序,当程序开始运行就称其为进程Linux系统中提供了3个API来给我们使用,用于创建新的进程
fork() —> sys_fork()
vfork() —> sys_vfork()
clone() —> sys_clone()

fork:

asmlinkage long sys_fork(struct pt_regs *regs){ return do_fork(SIGCHLD, regs->rsp, regs, 0, NULL, NULL); }

vfork:

asmlinkage long sys_vfork(struct pt_regs *regs){ return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->rsp, regs, 0,NULL, NULL);}

clone:

asmlinkage long sys_clone(unsigned long clone_flags, unsigned long newsp, void__user *parent_tid, void__user *child_tid, struct pt_regs *regs){ if (!newsp) newsp = regs->rsp; return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid); }

对于其中的clone()其实创建的不是真的线程,而是轻量级进程而对于线程其实还有两种即内核线程和用户线程,其中用户线程是利用pthread_create进行创建,且要链接外部库pthread.h


进程描述符PCB

通常所说的进程包括3个部分,即:程序段、数据段和进程描述符(PCB),PCB有着很重要的地位,对于do_fork()这个函数所做的实际上就是对PCB的创建。PCB在内核里就是task_struct结构体,而这个结构体的成员有许多,这里记录下几个主要的成员

state:进程运行状态thread_info : 指向线程描述符(或者直接看成是个简单的进程描述符)
mm : 指向mm_struct结构体,对进程用户空间的描述
real_parent : 养父进程(如果父进程早死亡会换)
parent : 父进程(始终是创建的那个,不会变)
tty : 指向tty_struct结构体,与进程相关的终端
fs : 指向fs_struct,对进程当前所在的目录描述
files : 指向files_struct,对进程已打开的所有文件进行描述

task_struct、thread_info和内核栈:

当进程从用户态进入内核态后都要使用栈,这个就是进程的内核栈,Linux中将内核栈与thread_info放在一起,占用8KB即2页的空间,其中栈从高地址向下增长,而thread_info存于底部。
thread_info相比于task_struct要小很多,所以为了给内核栈留下空间,只能选择thread_info与内核栈存于一起,而thread_info中的task即指向了进程的task_struct,而task_struct中的thread_info字段指向了这个与内核栈存在一起的结构体,于是当在代码中要用到task_struct地址时,就可以使用current 宏,相当于找到thread_info并通过它的task指针得到当前运行的进程的task_struct


do_fork

首先看下do_fork()的各种参数:

long do_fork(unsigned long clone_flags,unsigned long stack_start,struct pt_regs *regs,unsigned long stack_size,int__user *parent_tidptr,int__user *child_tidptr)

clone_flags:表示创建标志,对于用户创建进程的各种要求都通过此标志来传达给系统,这是最重要的参数多次使用
stack_start:表示子进程用户态堆栈的地址
regs:指向pt_regs结构体,用户进程从用户态进入内核态时用此结构体存寄存器的值
stack_size:表示堆栈的大小
parent_tidptr、child_tidptr:两者是父、子进程在用户态下pid地址

主要流程:

1.定义指向task_sturct结构体的指针,用于存储新建进程 定义跟踪信号 获取pid ———后面判断pid是否正确,<0则错误退出 判断当前进程(父)的描述符中ptrace字段是否为1(unlikely即基本是0),表示是否跟踪,跟踪则检查跟踪标志,返回一个定义好的值给trace,且设置跟踪标志表示跟踪新建进程(子)

struct task_struct *p; int trace = 0; long pid = alloc_pidmap(); if (pid < 0) return -EAGAIN; if (unlikely(current->ptrace)) {trace = fork_traceflag (clone_flags);  if (trace)  clone_flags |= CLONE_PTRACE; }

2.最关键的步骤:调用copy_process()
copy_process的参数在2.6.11的内核中相比于do_fork()是多了pid,即刚刚获取的新建进程的id号实现的功能即创建新的进程描述符PCB,且也创建了子进程的thread_info和内核栈等子进程运行所需要的其他的结构函数返回新建进程的进程描述符的地址

p=copy_process(clone_flags,stack_start,regs,stack_size,parent_tidptr, child_tidptr, pid);

3.判断返回的地址正确与否,通过IS_ERR()函数进行检测
如果错误,则释放之前分配的pid,并通过PTR_ERR()的到错误代码,保存在pid中作为返回值;
如果正确,则首先创建一个completion结构体,此结构体主要是用来实现同步的,其定义了两个成员,一个done变量来表示等待事件的完成与否,第二个成员则是一个等待队列,用于存放等待该事件完成的进程,之后判断几个标志:
如果创建标志中含有CLONE_VFORK标志,则父进程阻塞,放入等待队列中,直到子进程执行exec族函数或者是exit函数;
如果创建标志中含有PT_PTRACED或者CLONE_STOPPED标志表示跟踪子进程或者是设置子进程为TASK_STOPPED状态,则通过sigaddset函数增加挂起信号,挂起子进程;
如果trace变量不是0,即进程正在被跟踪,则将子进程的pid放入父进程的ptrace_message,并调用ptrace_notify()函数停止当前程序运行,并向父进程发送SIGCHLD信号

 if (!IS_ERR(p)) { struct completion vfork;        if (clone_flags & CLONE_VFORK) {            p->vfork_done = &vfork;            init_completion(&vfork);        }        if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {            sigaddset(&p->pending.signal, SIGSTOP);            set_tsk_thread_flag(p, TIF_SIGPENDING);        }        if (!(clone_flags & CLONE_STOPPED))            wake_up_new_task(p, clone_flags);        else            p->state = TASK_STOPPED;        if (unlikely (trace)) {            current->ptrace_message = pid;            ptrace_notify ((trace << 8) | SIGTRAP);        }        if (clone_flags & CLONE_VFORK) {            wait_for_completion(&vfork);            if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);}    } else {        free_pidmap(pid);        pid = PTR_ERR(p);}

4.返回pid变量,即调用fork会返回的值

return pid;

do_fork结束,内核版本是2.6.11

0 0
原创粉丝点击