基于I386的Linux2.4.18进程创建的分析和实践

   报告内容
 一、           概述
进程是系统执行程序和分配资源的单位,是程序的一个执行实例,因此个动态的概念。许多进程可以并发的运行同一程序,这些进程共享内存中程序正文的单一副本,但每个进程有自己的单独的数据和堆栈区。一个程序可能有许多进程,而每一个进程又可以有许多子进程。依次循环下去,而产生子孙进程。
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等),然后进行一系列的复杂操作,使程序变成进程以供系统调用。在运行着的系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个 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, &regs, 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, &regs, 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, &regs, 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, &current->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程序,即上图最后一行。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值