通常我们在代码中调用fork()来创建一个进程或者调用pthread_create()来创建一个线程,创建一个进程需要为其分配内存资源,文件资源,时间片资源等,在这里来描述一下linux进程的创建过程及写时复制技术。

一写时复制

子进程和父进程通常拥有着不同的进程内存空间(线程除外),传统的unix在创建子进程后,会复制父进程的地址空间的所有内容,这就十分的低效,因为经常子进程会立即执行exec操作,创建一个崭新的内存空间,另外像进程代码段这样的内存,父子进程只是读,而没有写操作,完全可以共享,而不用去复制,这样会节省大量的时间。

写时复制机制就是在这个背景下产生的,子进程创建后,不会去复制所有的父进程的内存空间物理内存,通常只复制下页全局目录,并把所有父进程的物理页设置为写保护,这样当父子进程中有一个对物理页进行写时,就会触发写保护异常,就复制一下对应的物理页,加入到对应的页表中即可。

二clone(), fork(),vfork()

    fork(),vfork()系统调用都是通过clone()函数来实现的,clone()函数介绍如下:

函数原型:


int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配用户态堆栈空间,flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值:

标志                   含义

CLONE_PARENT  创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”

 CLONE_FS          子进程与父进程共享相同的文件系统,包括root、当前目录、umask

 CLONE_FILES     子进程与父进程共享相同的文件描述符(file descriptor)表

 CLONE_NEWNS  在新的namespace启动子进程,namespace描述了进程的文件hierarchy

 CLONE_SIGHAND  子进程与父进程共享相同的信号处理(signal handler)表

 CLONE_PTRACE  若父进程被trace,子进程也被trace

 CLONE_VFORK    父进程被挂起,直至子进程释放虚拟内存资源

 CLONE_VM          子进程与父进程运行于相同的内存空间

 CLONE_PID         子进程在创建时PID与父进程一致

 CLONE_THREAD   Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。

实现clone()系统调用的服务例程是sys_clone(),sys_clone()例程并没有fn和arg参数,clone()函数会把fn放在子进程堆栈的某个位置,该位置就是封装函数本身返回地址的存放位置,arg指针放在fn堆栈的下面,当封装函数结束时,cpu取出fn,执行fn(arg).


与系统调用clone功能相似的系统调用有fork,但fork事实上只是clone的功能的一部分,clone与fork的主要区别在于传递了几个参数,而当中最重要的参数就是conle_flags,下表是系统定义的几个clone_flags标志,同时child_stack传递的也是父进程的用户态堆栈,由于写时复制,会在父子进程对堆栈进行操作时进行复制。

标志 Value 含义

CLONE_VM 0x00000100 置起此标志在进程间共享地址空间

CLONE_FS 0x00000200 置起此标志在进程间共享文件系统信息

CLONE_FILES 0x00000400 置起此标志在进程间共享打开的文件

CLONE_SIGHAND 0x00000800 置起此标志在进程间共享信号处理程序

三sys_clone()服务例程源码解析

fork(),vfork(),clone()三个系统调用最后都是使用sys_clone()服务例程来完成了系统调用,sys_clone()服务例程会去调用do_fork()函数,主要的处理流程就在do_fork()中。

3.1do_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)
{
struct task_struct *p;
int trace = 0;
//查看pidmap_array查找新进程的pid
long pid = alloc_pidmap();
//查看当前进程是否被trace,设置新进程的trace状态,只要进程不是内核线程就应该被trace
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
//将父进程的相关信息,如地址空间,信号等信息复制到新建进程描述符上面
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
if (!IS_ERR(p)) {
struct completion vfork;
//如果该进程是vfork出来的,需要等待子进程结束或退出后再执行父进程
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
//如果该进程被追踪,或者设置了clone_stopped标记,给该进程发送STOP信号, 设置了CLONE_STOPPED标记的话,进程不能够立即执行,需要先stop下来,后面通过
向该进程发送SIG_CONT信号使其恢复执行
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
//未设置clone_stopped标记,唤醒新进程,此后新进程可以参与调度
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;
++total_forks;
//需要trace的话,向debug进程发送一个trace消息
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
//如果该进程是被vfork()出来的,父进程在此等待,等待子进程退出
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);
}
return pid;
}

3.2copy_process()

static task_t *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
int pid)
{
int retval;
struct task_struct *p = NULL; if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL); //clone出一个线程,线程必须共享信号处理函数等
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL); //共享信号处理,但不共享地址空间,clone_sighand表示子进程和父进程共享信号处理函数表,共享阻塞信号表以及挂起信号表
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL); if (retval)
goto fork_out;
retval = -ENOMEM;
//复制当前进程进程描述符 ,实质的工作就是把当前进程的task_struct, thread_info都复制到了子进程里面,在该函数里面也会分配进程描述符
p = dup_task_struct(current);
if (!p)
goto fork_out;
retval = -EAGAIN;
//检查是否进程数符合进程资源限制这个时候p->user其实和父进程中的p->user是一样的,因为在dup_task_struct里面对父进程的描述符进行了复制
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != &root_user)
goto bad_fork_free;
}
//增加统计信息和进程所属用户的引用计数
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes); //检查线程数是否超出了限制
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//新进程尚未调用exec
p->did_exec = 0;
//设置新进程的flag,主要是去除PF_SUPERPRIV,设置PF_FORKNOEXEC,根据clone_falgs设置task的trace字段
copy_flags(clone_flags, p);
p->pid = pid;
retval = -EFAULT;
if (clone_flags & CLONE_PARENT_SETTID)
if (put_user(p->pid, parent_tidptr))
goto bad_fork_cleanup; p->proc_dentry = NULL;
//初始化进程的子进程链表和挂入兄弟链表的节点
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
//初始化等待队列,在系统调用vfork()中会使用到
init_waitqueue_head(&p->wait_chldexit);
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);
spin_lock_init(&p->proc_lock); //为新进程清除掉有信号挂起的标记位,表明该进程现在没有信号尚未处理
clear_tsk_thread_flag(p, TIF_SIGPENDING);
//清除掉新进程的挂起信号表,初始化相应的信号队列,从这里的代码看来,父进程未处理完毕的信号不会遗传给子进程,oh,yeah!
init_sigpending(&p->pending);
: //跳过了很多代码,这些代码我们不关注,不影响对主体流程的理解


//设置新进程的tgid,若该新进程是一个轻量级进程即线程的话,设置其tgid为父进程的tgid,否则其tgid和其pid是一样的
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid; //下面主要是根据clone_flags的标志来决定子进程是否来共用内存描述符,文件描述符等,信号处理函数,信号及阻塞码等,copy_thread()则会为新的子进程内核栈复制父进程内核栈的寄存器信息,设置进程描述符中thread中的相关寄存器信息,为后续的进程切换做好准备,3.3介绍了copy_thread()函数。
if ((retval = copy_files(clone_flags, p)))
goto bad_fork_cleanup_semundo;
if ((retval = copy_fs(clone_flags, p)))
goto bad_fork_cleanup_files;
if ((retval = copy_sighand(clone_flags, p)))
goto bad_fork_cleanup_fs;
if ((retval = copy_signal(clone_flags, p)))
goto bad_fork_cleanup_sighand;
if ((retval = copy_mm(clone_flags, p)))
goto bad_fork_cleanup_signal;
if ((retval = copy_keys(clone_flags, p)))
goto bad_fork_cleanup_mm;
if ((retval = copy_namespace(clone_flags, p)))
goto bad_fork_cleanup_keys;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespace; //清除掉p->thread的TIF_SYSCALL_TRACE的标志位,以使ret_from_fork不会把系统调用结束的消息发送给trace进程
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
//设置exit signal,对于线程组来说,只有线程组的最后一个线程退出,整个线程组才退出,并向线程组的父进程发送child信号,exit_signal其实主要是用来表明该进程是否是线程组首进程:exit_signal!=-1
p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
p->pdeath_signal = 0;
p->exit_state = 0; //给新进程分配时间片,分配的额度是父进程的一半,同时禁止内核抢占
sched_fork(p); //设置进程的组长进程就是自己,这里的组长进程是指线程组的组长进程,在后面的处理中会把对轻量级进程的组长进程设为真正的线程组组长进程
p->group_leader = p;
INIT_LIST_HEAD(&p->ptrace_children);
INIT_LIST_HEAD(&p->ptrace_list);
/* Need tasklist lock for parent etc handling! */
write_lock_irq(&tasklist_lock); //子进程继承父进程的cpus_allowed字段,cpus_allowed字段指明进程可以在哪些cpu上运行
p->cpus_allowed = current->cpus_allowed;
//设置子进程的thread->cpu,当前正在执行sys_clone()服务例程的cpu
set_task_cpu(p, smp_processor_id());
if (sigismember(¤t->pending.signal, SIGKILL)) {
write_unlock_irq(&tasklist_lock);
retval = -EINTR;
goto bad_fork_cleanup_namespace;
} //对于轻量级线程而言,新建子进程的parent,real_parent进程是父进程的父进程,即线程的父进程是fork()出线程组组长进程的进程
if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
p->real_parent = current->real_parent;
else
p->real_parent = current;
p->parent = p->real_parent; if (clone_flags & CLONE_THREAD) {
spin_lock(¤t->sighand->siglock);
//对于线程而言,其线程组长进程就是其父进程的组长进程
p->group_leader = current->group_leader;
spin_unlock(¤t->sighand->siglock);
}
//把新进程插入到进程列表中,是组长进程,才插入到全局的进程链表,即以init_task为进程链表头的链表,但还是会把该进程放入到PIDTYPE的hash表中
SET_LINKS(p);
//如果进程必须被追踪,将该进程放入current的父进程即调试进程的跟踪列表中,同时子进程的父进程也被设置为调试进程
if (unlikely(p->ptrace & PT_PTRACED))
__ptrace_link(p, current->parent);
//将进程插入到PIDTYPE_PID的hash表中
attach_pid(p, PIDTYPE_PID, p->pid);
//将进程插入到PIDTYPE_TGID的hash标中
attach_pid(p, PIDTYPE_TGID, p->tgid);
//该进程为线程组组长进程
if (thread_group_leader(p)) {
//将该进程插入到PIDTYPE_PGID hash表中
attach_pid(p, PIDTYPE_PGID, process_group(p));
//将该进程插入到PIDTYPE_SID hash表中
attach_pid(p, PIDTYPE_SID, p->signal->session);
if (p->pid)
__get_cpu_var(process_counts)++;
} nr_threads++;
write_unlock_irq(&tasklist_lock);
retval = 0;


}

3.3copy_thread

int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
unsigned long unused,
struct task_struct * p, struct pt_regs * regs)
{
struct pt_regs * childregs;
struct task_struct *tsk;
int err; //得到子进程内核栈的栈底
childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;
//将保存在父进程内核栈中的寄存器内容复制到子进程的内核栈
*childregs = *regs;
//将子进程的eax寄存器值设为0,eax表示返回值
childregs->eax = 0;
//childregs->esp中存放的是子进程用户态的栈地址
childregs->esp = esp; //thread.esp存放的是内核态的栈顶地址
p->thread.esp = (unsigned long) childregs;
p->thread.esp0 = (unsigned long) (childregs+1);
//thread.eip存放的是内核态的返回地址
p->thread.eip = (unsigned long) ret_from_fork; savesegment(fs,p->thread.fs);
savesegment(gs,p->thread.gs); tsk = current;
//copy父进程的io权限位图
if (unlikely(NULL != tsk->thread.io_bitmap_ptr)) {
p->thread.io_bitmap_ptr = kmalloc(IO_BITMAP_BYTES, GFP_KERNEL);
if (!p->thread.io_bitmap_ptr) {
p->thread.io_bitmap_max = 0;
return -ENOMEM;
}
memcpy(p->thread.io_bitmap_ptr, tsk->thread.io_bitmap_ptr,
IO_BITMAP_BYTES);
}
:
:
return err;
}

四do_fork()之后发生了什么


现在我们有了完整的可以运行的进程,但还有对其进行调度,在以后的进程切换时,会对其进行完善,将子进程描述符中thread字段的值放入几个寄存器中,主要是将thread.esp放入esp寄存器中,把ret_from_fork()函数的地址放入到eip寄存器中,(参看上面的copy_thread函数)然后进程切换后会去执行ret_from_fork()函数,ret_form_fork()回去调用schedule_tail()函数,用存放在内核栈中的值装载所有的寄存器,并强迫cpu返回用户态。系统调用的返回值存放在了eax中,返回给子进程的是0,父进程的是子进程的id号。

linux进程解析--进程的创建的更多相关文章

  1. linux进程解析--进程切换

    为了控制进程的执行,linux内核必须有能力挂起正在cpu上运行的进程,换入想要切换的进程,也就是恢复以前某个挂起的进程,这就是linux的进程切换.  1进程切换的时机 一般来说,进程切换都是发生在 ...

  2. linux进程解析--进程的退出及销毁

    一进程的退出: 当一个进程运行完毕或者因为触发系统异常而退出时,最终会调用到内核中的函数do_exit(),在do_exit()函数中会清理一些进程使用的文件描述符,会释放掉进程用户态使用的相关的物理 ...

  3. 《Linux内核分析》第六周 进程的描述与创建

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK SIX(3 ...

  4. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...

  5. Linux内核设计第六周 ——进程的描述和创建

    Linux内核设计第六周 ——进程的描述和创建 第一部分 知识点总结 一.进程描述符task_struct数据结构 1.操作系统的三大功能: 进程管理.内存管理.文件系统 2.进程的作用: 将信号.进 ...

  6. linux进程编程:子进程创建及执行函数简介

    linux进程编程:子进程创建及执行函数简介 子进程创建及执行函数有三个: (1)fork();(2)exec();(3)system();    下面分别做详细介绍.(1)fork()    函数定 ...

  7. linux 进程(二) --- 进程的创建及相关api

    一.进程的创建fork()函数  由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进 ...

  8. [进程管理]Linux进程状态解析之T、Z、X

             Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态.          向进程发送一个SIGSTOP信号,它就会因响应该信号而进入 ...

  9. Linux进程-进程的创建

    今天学习了Linux的进程创建的基本原理,是基于0.11版本核心的.下面对其作一下简单的总结. 一.Linux进程在内存中的相关资源   很容易理解,Linux进程的创建过程就是内存中进程相关资源产生 ...

随机推荐

  1. 解决Spring中singleton的Bean依赖于prototype的Bean的问题

    在spring bean的配置的时候,可能会出现一个singleton的bean依赖一个prototype的bean.因为singleton的bean只有一次初始化的机会,所以他们的依赖关系页只有在初 ...

  2. cocos2dx进阶学习之CCApplication

    继承关系 CCApplication-> CCApplicationProtocol 类主要成员变量 static CCApplication * sm_pSharedApplication; ...

  3. php 遍历文件夹及文件,获取文件名和文件路径存入数据库中

    <?php header("Content-Type:text/html; charset=gbk"); require('../../include/connect.php ...

  4. 解决screen Cannot open your terminal '/dev/pts/1'问题

    转载于:http://urchin.blog.51cto.com/4356076/1153322 问题描述: userA首先登录系统,使用screen开启了一个session,然后detach这个窗口 ...

  5. Website 内容管理

    公司网站后是这样的. 用的是中企动力的,后台还是比较好管理的.所以不多说什么了.

  6. jquery validate 插件使用小结

    项目中整合了jquery validate插件,仿照别人的使用写了我的表单验证,结果不起作用.然后就各种找原因. 在网上下了jquery validate插件的完整包,看了看里边的例子,跟我的使用貌似 ...

  7. 在Windows上使用CodeLite+MinGW+Clang进行开发

    前几天听说clang 3.4已经release了,然后我又手痒就折腾一下,在这里记录一下折腾的经过. 在以前就试过clang-cl+VC的开发环境,编译代码到是没发现什么大问题,有不少警告而已,不过c ...

  8. 三家DirectUI的商业公司

    目前正在研究DirectUI技术,分享一点心得给大家.关于DirectUI技术的介绍我在这里就不说了,可以上Google查一下,非常丰富.目前使用DirectUI技术开发的软件产品原来原丰富,比如QQ ...

  9. 将 mp3 等音乐资源以资源形式嵌入 exe 文件中

    引用:http://www.easyx.cn/skills/View.aspx?id=6 本文讲解怎样将 mp3 等音乐资源以资源形式嵌入 exe 文件中,并通过 mciSendString 调用.嵌 ...

  10. boost uuid 学习笔记

    #include <vector>#include <iostream>#include <boost/uuid/uuid.hpp>#include <boo ...