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

fork,vfork以及exec的意义

 
阅读更多

linux中创建进程是直接的,就是简单的一个fork调用,linux认为进程就是执行的一个任务,并没有和可执行文件联系起来,如果非要和可执行文件 联系的话就要涉及到另一个系统调用exec.linux这么实现取得了很大的灵活性,这个灵活性实际上也是继承unix的,因为unix就是这样实现进程的,进程在unix或者类unix比如linux系统中就是一个执行续,而不是别的什么特殊的东西,这使得在这些系统中很容易就实现了线程的概念,而且可 以用统一的管理系统去管理线程或者进程,所不同的仅仅是进程拥有另外一些东西而线程却没有,线程成了进程的完全子集,这在设计上是很重要的.相 反,windows内核虽然完全实现了线程,但是它实现的线程却和进程的概念是完全分离的,唯一的相同点就是都是nt执行体的"可执行"对象,统一于对象 管理器,继承了可执行对象的一切特性.这样windows实现的现代进程和线程的概念就相当松散,不利于统一管理,这完全体现在用户接口上,我们看一下 windows的进程创建接口;
BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
这 里仅仅给出接口声明和简单的解释,更详细的请查阅MSDN,第一个参数是可执行模块的名称字符串,第二个是命令行,...就不多说了,看得晕吗?我是不喜 欢这种方式了,为什么进程一定要有可执行模块?去问微软吧!这也可能是微软的策略,故意让你进入他自己的开发模式,然后上瘾然后你就离不开微软了...; 再看一眼它的线程创建接口:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
这个线程接口倒是比进程接口来的更直接,但是还是限制了很多东西,比如这回倒不必调用可执行模块了,但是为什么线程一定要有一个函数指针呢?
回归进程和线程的本意,进程是程序在计算机上的一次执行活动;线程是程序中一个单一的顺序控制流程。进程一定要有可执行模块吗?线程一定要有函数指针吗?都是不必的,可是提供给进程一 个可执行模块提供给线程一个函数指针会使得概念理解起来更容易,但是好处也仅仅如此。看看linux的方式吧,unix也同样。
linux中的进程和线程接口一样,都是fork,并且没有参数,那怎么区分呢?linux提供了另一个系统调用接口clone,但是clone本质上也 是用的fork,就是多加了一个参数用来识别要创建的是一个线程。fork在内核中实现为sys_fork,clone的内核实现为sys_clone, 我们看一眼它们的形式:

asmlinkage int sys_fork(struct pt_regs regs)

{

return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);

}

asmlinkage int sys_clone(struct pt_regs regs)

{

unsigned long clone_flags;

unsigned long newsp;

int __user *parent_tidptr, *child_tidptr;

clone_flags = regs.ebx;

newsp = regs.ecx;

parent_tidptr = (int __user *)regs.edx;

child_tidptr = (int __user *)regs.edi;

if (!newsp)

newsp = regs.esp;

return do_fork(clone_flags, newsp, &regs, 0, parent_tidptr, child_tidptr);

}

可 以看到二者都调用了do_fork,do_fork的代码就不分析了,这不是本文的主题,要注意的是do_fork的第一个参数是个标志,这个标志告诉内 核要创建的执行续是什么性质的,其中一个标志是CLONE_THREAD,就是要创建线程,而别的就是创建进程,当然标志是很丰富的,值得细细推敲。除了 标志没有任何限制,没有让用户提供什么别的东西,这其实是真正的进程/线程创建,试想有时候我仅仅想开辟一块内存然后让一个任务作为一个进程不被打扰得运 行,拥有自己的地址空间等等,而我没有关于这个任务的任何可执行文件。进程就是拥有独立的空间,是一个容器,而线程就是和别的同一进程的线程们共享该进程的空间和信号等等,至于说和可执行文件还有函数指针的联系是后来人们加上的,微软还真当成事儿了,当成规则标准了,这不是误人子弟吗?
那么linux要执行可执行文件该咋办呢?当然有办法了,不过要知道,linux认为一旦你执行了exec,你就是放弃了当前的进程地址空间而使用了新的地址空间来加载exec需要的可执行文件,这和有些文章说的地址空间蒸发不谋而合,但是不是蒸发了,而是被放弃了,仅仅是递减了该地址空间的引用计数而已,如果就一个task_struct使用了该mm_struct,那么它就被释放了,如果很多线程共享这个mm_struct,那么也就这个执行 exec的线程放弃了该mm_struct,对别的线程没有任何影响,可以看一下代码:

int do_execve(char * filename,

char __user *__user *argv,

char __user *__user *envp,

struct pt_regs * regs)

{

...

bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);

...

bprm->file = file;

bprm->filename = filename;

bprm->interp = filename;

bprm->mm = mm_alloc(); //bprm申请了一个mm_struct

...

retval = search_binary_handler(bprm,regs);

...

}

我们看到专门为一个可执行的二进制文件申请了一个地址空间mm_struct,这个地址空间将来要替换当前进程的地址空间,我们马上看到这一幕,在search_binary_handler 最终要调用elf的加载函数load_elf_binary,在该函数里实现了替换,调用了retval = flush_old_exec(bprm)我们看一下这个非常的retval = flush_old_exec(bprm);

int flush_old_exec(struct linux_binprm * bprm)

{

...

retval = exec_mmap(bprm->mm); //这个函数最终实现了替换

...

set_task_comm(current, tcomm);

current->flags &= ~PF_RANDOMIZE;

flush_thread();

current->mm->task_size = TASK_SIZE;

...

current->self_exec_id++;

flush_signal_handlers(current, 0);

flush_old_files(current->files);

...

}

在exec_mmap中有几个重要的操作实现了替换,我们看一下:
tsk = current;
old_mm = current->mm;
mm_release(tsk, old_mm);
active_mm = tsk->active_mm;
tsk->mm = mm;
tsk->active_mm = mm;
activate_mm(active_mm, mm);
这几个就不用多说了,这里的mm_release就是所谓的蒸发,希望大家通过这个对地址空间的蒸发知其所以然。
最后说一下另一个系统调用vfork,vfork不实现写时复制,而是阻塞当前调用线程,其间和被阻塞的进程公用地址空间,只有到子进程调用exec或exit时才唤醒被阻塞线程,这是为何呢?因为当子进程执行exec时,它放弃了地址空间,所以原线程就又可以用了

分享到:
评论

相关推荐

    【Linux】进程二 (PCB & fork/vfork & wait/waitpid & exit/_exit & exec函数族 & 环境变量)

    一、描述进程——PCB ·进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合 ·我们称为PCB,Linux操作系统下的PCB是:task struct 2、task_struct——PCB的一种 ·在Linux中描述进程的结构体...

    深入解读Linux进程函数fork(),vfork(),execX()

    本文研究的主要是Linux进程函数fork(),vfork(),execX()的相关内容,具体介绍如下。 函数fork() fork函数:创建一个新进程 1、fork()成功后,将为子进程申请PCB和用户内存空间。 2、子进程会复制父进程用户空间的...

    Linux 创建子进程执行任务的实现方法

    本文将介绍如何使用 fork/vfork 系统调用来创建新进程并使用 exec 族函数在新进程中执行任务。 fork 系统调用 要创建一个进程,最基本的系统调用是 fork: # include pid_t fork(void); pid_t vfork(void); 调用 ...

    《linux编程技术》-实验3.doc

     3,要求分别用fork和vfork创建子进程。 实验五  创建子进程一,在子进程中递归打印/home目录中的内容(用exec系列函数调用第二次实验中的代码完成此功能);  1.子进程结束的时候完成以下功能:  打印字符...

    unix 编程进程控制详细介绍

    unix 编程进程控制: fork函数 fork创建子进程,子进程是父进程的副本,会...1:vfork子进程先执行,并且子进程调用exec函数 2:vfork子进程不会copy父进程的地址空间,也就是会公用。 exit函数 exit函数会关闭所以I

    UNIX环境高级编程(第八章)

    8.3 fork函数 142 8.4 vfork 函数 145 8.5 exit函数 147 8.6 wait和waitpid函数 148 8.7 wait3和wait4函数 152 8.8 竞态条件 153 8.9 exec函数 156 8.10 更改用户ID和组ID 160 8.10.1 setreuid 和setregid函数 162 ...

    posix-spawn:Ruby进程生成库

    在fork()的许多常见用法中,其后是exec函数家族之一以生成子进程( Kernel#system , IO::popen , Process::spawn等),可以消除此开销通过使用特殊的进程生成接口( posix_spawn() , vfork()等) posix-spawn...

    UNIX环境高级编程_第二版中文

    8.4 vfork函数  8.5 exit函数  8.6 wait和waitpid函数  8.7 waitid函数  8.8 wait3和wait4函数  8.9 竞争条件  8.10 exec函数  8.11 更改用户ID和组ID  8.12 解释器文件  8.13 system函数  8.14...

    UNIX高级编程 计算机科学丛书

    8.4 vfork 函数 8.5 exit函数 8.6 wait和waitpid函数 8.7 wait3和 wait4函数 8.8 竞态条件 8.9 exec函数 8.10 更改用户ID 和组ID 8.10.1 setreuid 和setregid函数 8.10.2 seteuid和 setegid函数 8.10.3 组ID 8.11 ...

    UNIX环境高级编程 pdf格式

    8.4 vfork 函数 145 8.5 exit函数 147 8.6 wait和waitpid函数 148 8.7 wait3和wait4函数 152 8.8 竞态条件 153 8.9 exec函数 156 8.10 更改用户ID和组ID 160 8.11 解释器文件 164 8.12 system函数 167 8.13 进程会计...

    UNIX环境高级编程(第二版中文)

    8.4 vfork函数 176 8.5 exit函数 178 8.6 wait和waitpid函数 179 8.7 waitid函数 183 8.8 wait3和wait4函数 184 8.9 竞争条件 185 8.10 exec函数 188 8.11 更改用户ID和组ID 192 8.12 解释器文件 ...

    unix环境编程电子书

    164 7.12 小结 168 习题 168 第8章 进程控制 171 8.1 引言 171 8.2 进程标识符 171 8.3 fork函数 172 8.4 vfork函数 176 8.5 exit函数 178 8.6 wait和waitpid函数 179 8.7 waitid函数 183 8.8 ...

    UNIX环境高级编程

    8.4 vfork函数 176 8.5 exit函数 178 8.6 wait和waitpid函数 179 8.7 waitid函数 183 8.8 wait3和wait4函数 184 8.9 竞争条件 185 8.10 exec函数 188 8.11 更改用户ID和组ID 192 8.12 解释器文件 ...

    UNIX环境高级编程(第二版,英文版)

    vfork Function Section 8.5. exit Functions Section 8.6. wait and waitpid Functions Section 8.7. waitid Function Section 8.8. wait3 and wait4 Functions Section 8.9. Race ...

    UNIX环境高级编程部分

    8.4 vfork 函数 8.5 exit函数 8.6 wait和waitpid函数 8.7 wait3和 wait4函数 8.8 竞态条件 8.9 exec函数 8.10 更改用户ID 和组ID 8.10.1 setreuid 和setregid函数 8.10.2 seteuid和 setegid函数 8.10.3 组ID 8.11 ...

    UNIX环境高级编程英文第三版+源码

    8.10 exec Functions 249 8.11 Changing User IDs and Group IDs 255 8.12 Interpreter Files 260 8.13 system Function 264 8.14 Process Accounting 269 8.15 User Identification 275 8.16 Process Scheduling ...

    UNIX环境高级编程_第2版.part1

     此基础上介绍了多个应用实例,包括如何创建数据库函数库以及如何与网络打印机通信等。此外,还在附  录中给出了函数原型和部分习题的答案。  本书内容权威,概念清晰,阐述精辟,对于所有层次UNIX/Linux程序员...

Global site tag (gtag.js) - Google Analytics