报告内容
一、
概述
进程是系统执行程序和分配资源的单位,是程序的一个执行实例,因此个动态的概念。许多进程可以并发的运行同一程序,这些进程共享内存中程序正文的单一副本,但每个进程有自己的单独的数据和堆栈区。一个程序可能有许多进程,而每一个进程又可以有许多子进程。依次循环下去,而产生子孙进程。
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等),然后进行一系列的复杂操作,使程序变成进程以供系统调用。在运行着的系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个
ID(就象我们的身份证号),以便识别。
二、
linux
创建进程的三种方式
Linux提供了几个系统调用来创建进程,它们是:Fork、vfork和clone。它们通过统一的系统调用接口进人核心态运行,各自调用自己的内核函数Sys_fork(),sys_clone(),sys_vfork()进行处理。这三个函数最终都是调用do_fork()函数进行创建子进程的工作,只不过使用的参数有所不同。
1. fork
fork系统调用创建一个新进程,我们称调用fork的进程称为父进程。被创建的为子进程,子进程几乎是父进程的完全复制――它的地址空间是父进程的复制,一开始也是运行同一程序。不过,fork系统调用为父子进程返回不同的值。
定义如下:
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0);
}
|
2. clone
定义如下:
asmlinkage int sys_clone(struct pt_regs regs) { unsigned long clone_flags; unsigned long newsp; clone_flags = regs.ebx; newsp = regs.ecx; if (!newsp) newsp = regs.esp; return do_fork(clone_flags, newsp, ®s, 0); } |
clone是 3个系统调用中最灵活的,因为这个函数可以通过参数指定具体的创建标志,并且把变量并最终传递给do_fork()。这个系统调用既能创建用户进程,又能创建用户态线程。
3. vfork
定义如下:
asmlinkage int sys_vfork(struct pt_regs regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0); } |
vfork的主要功能是创建用户态轻量级进程,从其创建标志CLONE_VM可以看出它和父进程共享用户态空间,而CLONE_VFORK则用于父子进程间的同步。另外子进程结束运行时要向父进程发送SIGHLD信号。vfork()不必从父进程那里复制大量的内存页表和内存页。
三、
do
_fork()
源码分析
/*
*
第
1
个参数
clone_flags(
长整型变量
)
是创建标志和子进程退出信号
*
的集合,
stacks_tart
是新进程的堆栈指针,
regs
指向调用函数的进程
*(
父进程
)
的堆栈,
stack size
是堆栈大小
*/
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
{
int retval; //
设置一个返回值字段
struct task_struct *p;
struct completion vfork;
retval = -EPERM; //
初始化返回值
……
//
省略多处理器处理段
/*
*ENOMEM
:对创建新进程来说没有足够的空间,该错误是指
*
没有足够的空间分配给必要的内核结构。
*/
retval = -ENOMEM;
/*
*
以获得
8KB
的
union task_union
内存区来存放进程描述符和新进程的内核态堆栈
*/
p = alloc_task_struct();
if (!p)
goto fork_out;
*p = *current;
/*
* EAGAIN
:系统调用
fork
不能得到足够的内存来拷贝父进
*
程页表。或用户是超级用户但进程表满,或者用户不是超
*
级用户但达到单个用户能执行的最大进程数。
*/
retval = -EAGAIN;
/* *检测创建的进程数目是否超过了限制,超过则跳转到失败 */
if (atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur
&& !capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE))
goto bad_fork_free;
/*如果没有超过,则进程计数递增*/
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);
/* * 以下还是计数相关,包括线程数、模块数目等 */
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
get_exec_domain(p->exec_domain);
if (p->binfmt && p->binfmt->module)
__MOD_INC_USE_COUNT(p->binfmt->module);
p->did_exec = 0;
p->swappable = 0; //
进程未创建完不可中断
p->state = TASK_UNINTERRUPTIBLE;
/*根据clone flags修改新进程的标志,更新一些从父进程拷贝来的标志域的标志*/
copy_flags(clone_flags, p);
p->pid = get_pid(clone_flags); //
为进程分配一个没有使用过的
PID
p->run_list.next = NULL;
p->run_list.prev = NULL;
p->p_cptr = NULL;
init_waitqueue_head(&p->wait_chldexit);
p->vfork_done = NULL;
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
spin_lock_init(&p->alloc_lock);
p->sigpending = 0; //
挂起状态置为
0
init_sigpending(&p->pending); /
/
初始化信号队列
p->it_real_value = p->it_virt_value = p->it_prof_value = 0;
p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;
init_timer(&p->real_timer);
p->real_timer.data = (unsigned long) p;
p->leader = 0; /* session leadership doesn't inherit */
p->tty_old_pgrp = 0;
p->times.tms_utime = p->times.tms_stime = 0;
p->times.tms_cutime = p->times.tms_cstime = 0;
#ifdef CONFIG_SMP
{
int i;
p->cpus_runnable = ~0UL;
p->processor = current->processor;
/* ?? should we just memset this ?? */
for(i = 0; i < smp_num_cpus; i++)
p->per_cpu_utime[i] = p->per_cpu_stime[i] = 0;
spin_lock_init(&p->sigmask_lock);
}
#endif
p->lock_depth = -1; /* -1 = no lock */
p->start_time = jiffies;
INIT_LIST_HEAD(&p->local_pages);
retval = -ENOMEM;
/*根据参数flag,调用copy_file(),copy_fs(),copy_sighand()和copy_mm() *来创建新的数据结构,并把父进程相应的数据结构拷贝过来
*/
if (copy_files(clone_flags, p))
goto bad_fork_cleanup;
if (copy_fs(clone_flags, p))
goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p))
goto bad_fork_cleanup_fs;
if (copy_mm(clone_flags, p))
goto bad_fork_cleanup_sighand;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_mm;
p->semundo = NULL;
p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */
p->swappable = 1;
p->exit_signal = clone_flags & CSIGNAL;
p->pdeath_signal = 0;
p->counter = (current->counter + 1) >> 1;
current->counter >>= 1;
if (!current->counter)
current->need_resched = 1;
retval = p->pid;
p->tgid = retval;
INIT_LIST_HEAD(&p->thread_group);
/* Need tasklist lock for parent etc handling! */
write_lock_irq(&tasklist_lock);
/* CLONE_PARENT and CLONE_THREAD re-use the old parent */
p->p_opptr = current->p_opptr;
p->p_pptr = current->p_pptr;
if (!(clone_flags & (CLONE_PARENT | CLONE_THREAD))) {
p->p_opptr = current;
if (!(p->ptrace & PT_PTRACED))
p->p_pptr = current;
}
if (clone_flags & CLONE_THREAD) {
p->tgid = current->tgid;
list_add(&p->thread_group, ¤t->thread_group);
}
SET_LINKS(p);
hash_pid(p);
nr_threads++;
write_unlock_irq(&tasklist_lock);
if (p->ptrace & PT_PTRACED)
send_sig(SIGSTOP, p, 1);
/*
*子进程状态设置成TASK_RUNNING,并调用
*wake_up_process()
把子进程插入到运行队列中
*/
wake_up_process(p); /* do this last */
++total_forks;
if (clone_flags & CLONE_VFORK)
wait_for_completion(&vfork);
fork_out:
return retval;
//
返回子进程的
PID
,这个
PID
最终由用户态下的父进程读取
bad_fork_cleanup_mm:
exit_mm(p);
bad_fork_cleanup_sighand:
exit_sighand(p);
bad_fork_cleanup_fs:
exit_fs(p); /* blocking */
bad_fork_cleanup_files:
exit_files(p); /* blocking */
bad_fork_cleanup:
put_exec_domain(p->exec_domain);
if (p->binfmt && p->binfmt->module)
__MOD_DEC_USE_COUNT(p->binfmt->module);
bad_fork_cleanup_count:
atomic_dec(&p->user->processes);
free_uid(p->user);
bad_fork_free:
free_task_struct(p);
goto fork_out;
}
|
从以上的源代码分析中可以看出:Linux创建进程的过程其实就是复制父进程的过程,包括进程结构、核心堆栈、数据、信号、空间等等,根据传递过来的不同的参数,需要共享的就共享(增加相应对象的计数),不能共享的就分配新的对象内存并且从父进程那里复制对象内容。
实验内容
使用C语言编写一段用户程序,调用fork()创建一个子进程,然后让子进程和父进程分别输出fork()的返回值。
目的:从用户态体验进程的创建
实验程序(如果有程序,需要附上)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
int main()
{
pid_t pid;
printf("Process Fork Test!/n"); //
首先由父进程执行程序代码
pid=fork(); //fork
系统调用创建子进程
if(pid==-1) //
若返回
-1
则创建失败
printf("Failed!/n");
else if(pid==0) //
子进程执行代码
{
printf("The following is the child process./n");
printf("The child PID is %d/n",getpid()); //
获得子进程
pid
printf("The return number is %d/n",pid);
exit(0); //
子进程结束
}
else //
父进程执行代码
{
printf("The following is the parent process./n");
printf("The parent PID is %d/n",getpid()); //
获得父进程
pid
printf("The return number is %d/n",pid); //
父进程掉用
fork()
的返回值
}
printf("The child process is over, parent process continue.../n"); //
父进程可继续执行
}
|
实验结果
如图所示:
实验总结
系统调用fork用来创建一个子进程。当调用执行成功时,该调用对父进程返回子进程的PID,而对子进程返回0。如果调用失败时,给父进程返回-1,没有子进程创建。这些由程序注释说明。
在语句pid=fork();执行前,只有一个进程在执行这段代码,即父进程执行。但在这条语句之后后,就变成两个进程在执行了,这两个进程的代码部分完全相同,将要执行的下一条语句都是if(pid==-1)。两个进程中,原先存在的进程为“父进程”,新出现的那个为“子进程”。父子进程的区别除了进程标示符(Process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值,由实验结果图显示可知。fork调用的奇妙之处就是它仅仅被调用一次,却能够返回两次。
从上面程序运行的结果可以看出进程的调度,父进程在打印出首句提示信息后,进行了fork()系统调用;随后CPU调度新创建的子进程打印子进程的提示信息,然后进行exit()系统调用,子进程结束;调度父进程执行,父进程执行完,将控制返还给shell程序,即上图最后一行。