进程的创建

进程的创建也就有两种方式:一是由操作系统创建,二是由父进程创建。
在系统启动时,操作系统会创建一些进程,他们承担着管理和分配系统资源的任务,这些进程通常被称为 系统进程
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构。整个Linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,他是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟贮存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号。。的若干终端注册进程getty。每个getty进程设置其进程组标识号,并检测配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登陆过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。
上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程.
 
注意,上述过程描述中提到:1号内核进程调用执行init并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:
 
1.init()函数在内核态运行,是内核代码
2.init进程是内核启动并运行的第一个用户进程,运行在用户态下
3.init()函数调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。
 
那么用户如何从当前进程创建一个新的进程呢?
fork,vfork,clone都是Linux的系统调用,用来创建子进程的,但是大家还是在使用时经常混淆,这里有具体的例子来讲解三者的区别于联系:
从上一篇中我们知道,进程由4个要素组成:
1、进程控制块:进程标志(task_struct)
2、进程程序块:可与其它进程共享
3、进程数据块:进程专属空间,用于存放各种私有数据以及堆栈空间
4、独立的空间(用户空间)(如果没有4则认为是线程)。
 
一、fork
fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。如
int main() {
        int num = 1;
        int child;

        if(!(child = fork())) { 
                printf("This is son, his num is: %d. and his pid is: %d\n", num, getpid());
        } else {
                printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
        }
   }

执行结果为:This is son, his num is: 2. and his pid is: 2139
This is father, his num is: 1, his pid is: 2138
从代码里面可以看出2者的pid不同,子进程改变了num的值,而父进程中的num没有改变
总结:优点是子进程的执行独立于父进程,具有良好的并发性。缺点是两者的通信需要专门的通信机制,如pipe、fifo和system V等。有人认为这样大批量的复制会导致执行效率过低。其实在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表,在子进程运行前,两者指向同一页面。而当子进程改变了父进程的变量时候,会通过copy_on_write的手段为所涉及的页面建立一个新的副本。因此fork效率并不低。
二、vfork
vfork函数创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见。这与fork是完全不同的,fork进程是独立的空间。另外一点不同的是vfork创建的子进程后,父进程会被阻塞,直到子进程执行exec()和exit()。如:
int main() {
        int num = 1;
        int child;

        if(!(child = vfork())) { 
                printf("This is son, his num is: %d. and his pid is: %d\n", num, getpid());
        } else {
                printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
        }
   }


运行结果为:This is son, his num is: 2. and his pid is:4139
This is father, his num is: 2, his pid is: 4138
从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的num变量,这一次是指针复制,2者的指针指向了同一个内存.
总结:当创建子进程的目的仅仅是为了调用exec()执行另一个程序时,子进程不会对父进程的地址空间有任何引用。因此,此时对地址空间的复制是多余的,通过vfork可以减少不必要的开销。
三、clone
函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择像vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。
总结:
clone, fork, vfork实现方式
大致相同: 系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成.
do_fork的参数与clone系统调用的参数类似, 不过多了一个regs(内核栈保存的用户模式寄存器). 实际上其他的参数也都是用regs取的
区别在于:
clone:  clone的API外衣, 把fn, arg压入用户栈中, 然后引发系统调用. 返回用户模式后下一条指令就是fn.
sysclone: parent_tidptr, child_tidptr都传到了 do_fork的参数中
sysclone: 检查是否有新的栈, 如果没有就用父进程的栈 (开始地址就是regs.esp)
fork, vfork:
服务例程就是直接调用do_fork, 不过参数稍加修改
用户栈: 都是父进程的栈.
parent_tidptr, child_ctidptr都是NULL.
用fork创建了子进程后,打印的结果是父子进程的执行顺序是不确定的,父子进程的输出时混杂在一起的,而使用vfork创建子进程后,打印的结果是子进程在前,父进程在后,说明vfork保证子进程先执行,在子进程调用exit或exec之前父进程处于阻塞等待状态。
如下所示的例子,可以观察父子进程的执行顺序和对父进程变量的修改说明两者的区别:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int globVar=5;
int main(void)
{
    pid_t pid;
    int globVar=5;
    int var=1,i;
    printf("fork is diffrent with vfork \n");
    pid=fork();
    switch(pid){
    
        case 0:
            i=3;
            while(i-->0)
            {
                printf("Child process is running \n");
                globVar++;
                var++;
                sleep(1);
            }
            printf("Child's globVar=%d,var=%d\n",globVar,var);
            break;
        case -1:
            printf("process creation failed\n");
            exit(0);
        default:
            i=5;
            while(i-->0)
            {
                printf("Parent process is running\n");
                globVar++;
                var++;
                sleep(1);

            }
            printf("Parent's globvar=%d,var=%d\n",globVar,var);
            exit(0);
    
    }
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值