Linux把进程的创建与目标程序的执行分为两步,一步为从已经存在的父进程中复制出子进
程,主要是通过fork(),clone(),vfork()。第二步是目标程序的执行,linux系统提供exe
cve()让进程执行以文件形式存在的一个可执行程序的映象。
有个函数例外,kernel_thread(),它看起来不是创建执行两步走,而是通俗意义上所说的
“一揽子”创建执行一起来的,它实际上是对clone()的封装,它并不象调用execve()时那
样执行一个可执行映象文件,而只是执行内核中某个函数。
/*
* Create a kernel thread
*/
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;
__asm__ __volatile__(
/*
* 这里采取一种比较特别的比较区别父子进程的方法
* 它将返回时的堆栈指针和保存在寄存器esi的父进程
* 堆栈指针进行比较,由于每个内核线程都有自己的系统
* 空间堆栈,子进程的堆栈指针必然和父进程不同,这也
* 导致了该方法只适合对内核线程适用,因为普通的进程
* 都在用户空间,根本不知道其系统空间堆栈在哪里
*/
/*将父进程的堆栈指针存放在esi寄存器中*/
"movl %%esp,%%esi/n/t"
/*系统调用,通过eax传递系统调用号*/
"int $0x80/n/t" /* Linux/i386 system call */
"cmpl %%esp,%%esi/n/t" /* child or parent? */
"je 1f/n/t" /* parent - jump */
/* Load the argument into eax, and push it. That way, it does
* not matter whether the called function is compiled with
* -mregparm or not. */
"movl %4,%%eax/n/t"
"pushl %%eax/n/t"
"call *%5/n/t" /* call fn */
/*再次装入系统调用号__NR_exit*/
"movl %3,%0/n/t" /* exit */
/*系统调用*/
"int $0x80/n"
"1:/t"
:"=&a" (retval), "=&S" (d0)
/*
* 在输入段将eax寄存器存放了__NR_clone,也就是第一个系统调用
* 为何会系统调用clone()函数的缘故.
* fn为被子进程调用执行的内核中的某个函数指针
*/
:"0" (__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
: "memory");
return retval;
}
系统调用fork(),__clone(),vfork()
__clone()是有选择的复制父进程的资源,fork()是完全全面的复制,vfork()除了task_s
truct()和系统空间堆栈外的资源全部是通过数据结构指针的复制遗传的,三个系统调用都
是通过do_fork()实现的。
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
{
int retval;
unsigned long flags;
struct task_struct *p;
struct completion vfork;
/*为什么如果flag位clone_fs或clone_newns置为1则返回参数错误???*/
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return -EINVAL;
retval = -EPERM;
/*
* CLONE_PID is only allowed for the initial SMP swapper
* calls
*/
/* __clone()调用时,如果标志位CLONE_PID为1时表明父子进程使用
* 同一个进程号,但只有0号进程才可以这样调用__clone()
*/
if (clone_flags & CLONE_PID) {
if (current->pid)
goto fork_out;
}
retval = -ENOMEM;
/*
* 分配两个连续的物理页面,存放task_struct()和系统空间内存
* 有关这个昨天的日志曾详细讲过的,^_^
*/
p = alloc_task_struct();
if (!p)
goto fork_out;
/*整个数据结构赋值,父进程的整个task_struct被复制*/
*p = *current;
p->tux_info = NULL;
retval = -EAGAIN;
/*
* Check if we are over our maximum process limit, but be sure to
* exclude root. This is needed to make it possible for login and
* friends to set the per-user process limit to something lower
* than the amount of processes root is running. -- Rik
*/
/* 该用户进程数目是否大于用户进程的RLIMINT_NPROC所允许的最大数目*/
if (atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur
&& !capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE))
goto bad_fork_free;
/* __cout++;process++ --princeC*/
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);
/*
* Counter increases are protected by
* the kernel lock so nr_threads can't
* increase under us (but it may decrease).
*/
/*checked for kernel thread --princeC*/
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
/*该进程所涉及到的模块计数器加1*/
get_exec_domain(p->exec_domain);
/* 同理,每个进程所执行的程序属于某种可执行映象格式
* 如a.out格式,elf格式,java虚拟机格式,对于这些不同格式的支持
* 往往是通过动态安装的驱动模块来实现的,这里将有关该
* 模块的计数器加一
*/
if (p->binfmt && p->binfmt->module)
__MOD_INC_USE_COUNT(p->binfmt->mod
进程的创建,执行和消亡。 (clone kernel_thread)
最新推荐文章于 2025-08-16 14:57:31 发布