通过 上一部分的学习,我们了解了进程的概念以及在Linux中进程的实现,此部分我们将具体学习如何在Linux中创建一个进程。
一前言:
通过原理知识的学习,我们知道每个进程由进程ID号标识。进程被创建时系统会为其分配一个唯一的进程ID号。当一个进程向其父进程(创建该进程的进程)传递其终止消息时,意味这个进程的整个生命周期结束。此时,该进程占用的所用资源包括进程ID被全部释放。
那么在Linux中如何创建一个进程呢?
创建进程有两种方式:一是由操作系统创建,二是由父进程创建的进程(通常为子进程)。
系统调用fork是创建一个新进程的唯一方式。vfork也可创建进程,但它实际上还是调用了fork函数。
Tiger-John 说明:
1.由操作系统创建的进程它们之间是平等的不存在资源继承关系。
2.由父进程创建的进程通常为子进程它们之间有继承关系。
3. 在系统启动时,OS会创建一些进程,它们承担着管理和分配系统资源的任务,即系统进程。
如0号idle进程,它是从无到有诞生的第一个线程,主要用于节能 ;关于 idle进程 , 系统最初引导 0号进程,对应的 PCB为 init_task(),要说明下它是 0号进程 PCB的头,并不是 1号 init进程,在引导结束后即成为 cpu 上的 idle进程。在每个 cpu上都有一个 idle进程,这些进程登记在 init_tasks[]数组中。 idle进程不进入就绪队列,系统稳定后,仅当就绪队列为空的时候 idle 进程才会被调度到,在没有其它进程运行的情况下,它大量时间占用 cpu 。
1号进程(init进程)它是一个由内核启动的用户级进程 ,它是所有用户进程的父进程。实际上,Linux2.6在初始化阶段首先把它建立为一个内核线程kernel_init:
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
参数CLONE_FS | CLONE_FILES | CLONE_SIGHAND表示0号线程和1号线程分别共享文件系统(CLONE_FS)、打开的文件(CLONE_FILES)和信号处理程序(CLONE_SIGHAND)。当调度程序选择到kernel_init内核线程时,kernel_init就开始执行内核的一些初始化函数将系统初始化。
那么,kernel_init()内核线程是怎样变为用户进程的呢?
实际上,kernel_init()内核函数中调用了execve()系统调用,该系统调用装入用户态下的可执行程序init(/sbin/init)。注意,内核函数kernel_init()和用户态下的可执行文件init是不同的代码,处于不同的位置,也运行在不同的状态,因此,init是内核线程启动起来的一个普通的进程,这也是用户态下的第一个进程。init进程从不终止,因为它创建和监控操作系统外层所有进程的活动。
二fork()函数和vfork()函数的学习
1.fork()函数
调用fork函数后,当前进程分裂为两个进程,一个是原来的父进程,另一个是刚创建的子进程。父进程调用fork后返回值是子进程的ID,子进程中返回值是0,若进程创建失败,只返回-1。失败原因一般是父进程拥有的子进程个数超过了规定限制(返回EAGAIN)或者内存不足(返回ENOMEM)。我们可以依据返回值判断进程,一般情况下调用fork函数后父子进程谁先执行是未定的,取决于内核所使用的调度算法。一般情况下os让所有进程享有同等执行权,除非某些进程优先级高。若有一个孤儿进程,即父进程先于子进程死去,子进程将会由init进程收养。
函数实例:通过fork()函数创建一个进程:
1 #include<sys/types.h>
2 #include<unistd.h>
3 #include<stdio.h>
4
5 main()
6 {
7 pid_t pid;
8 printf("PID before fork() :%d\n",(int)getpid());
9
10 pid = fork();
11 if(pid < 0){
12 printf("error in fork!\n");
13 }
14 else if(0 == pid){
15 printf("I'm the child process, CurpPID is %d,ParentPid is %d \n",pid,(int)getppid());
16 }
17 else{
18 printf("I'm the parent process,child PID is %d,ParentPID is %d\n",pid,(int)getpid());
19 }
20
程序经过调试后结果如下:
think@ubuntu:~/work/process_thread/fork$ ./fork
PID before fork() :4566
I'm the parent process,child PID is 4567,ParentPID is 4566
I'm the child process, CurpPID is 0,ParentPid is 4566
从程序执行结果可以看出: 调后fork()函数后返回两个值,子进程返回值为0,而父进程的返回值为创建的子进程的进程ID。
Tiger-John说明:
1> Linux进程一般包括代码段,数据段和堆栈段。代码段存放程序的可执行代码;数据段存放程序的全局变量、常量、静态变量;堆存放动态分配的内存变量,栈用于函数调用,存放函数参数、函数内部定义的局部变量。
2>有人说调用fork函数后,fork()函数返回了两个值,这是一中错误的说法。其实,当系统调用fork()函数后 fork()会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去, 当前进程分裂成了两个进程分别在执行,互不干扰。
3>.看一个函数实例来仔细体会一下系统调用fork()函数后是如何执行的。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t pid;
8 int count = 0;
9 pid = fork();
10
11 printf("This is first time,pid = %d\n",pid);
12 printf("This is the second time,pid = %d\n",pid);
13 count++;
14 printf("count = %d\n",count);
15
16 if(pid > 0){
17 printf("This is the parent process,the child has the pid :%d \n",pid);
18 }
19 else if(!pid){
20 printf("This is the child process.\n");
21 }
22 else{
23 printf("fork failed.\n");
24 }
25
26 printf("This is third,pid = %d\n",pid);
27 printf("This is four time,pid = %d\n",pid);
28 return 0;
29 }
程序经过调试后结果
think@ubuntu:~/work/process_thread/fork1$ ./fork
This is first time,pid = 4614
This is the second time,pid = 4614
count = 1
This is the parent process,the child has the pid :4614
This is third,pid = 4614
This is four time,pid = 4614
This is first time,pid = 0
This is the second time,pid = 0
count = 1
This is the child process.
This is third,pid = 0
This is four time,pid = 0
think@ubuntu:~/work/process_thread/fork1$
Tiger-John说明:
从上面的程序的执行结果我们看到一个奇怪的现象:为什么printf的语句执行两次,
而那句“count++;”的语句却只执行了一次 ?
系统在调用fork()后分裂成了两个函数分别执行 ,互不干扰 。
2.Vfork和fork的区别:
1> vfork也可创建进程,但它实际上还是调用了fork函数。
2>在说明他们的区别之前我们先看两个程序和执行结果
函数实例1:用fork()函数创建进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globVar = 5;
6
7 int main(void)
8 {
9 pid_t pid;
10 int var = 1;
11 int i;
12 printf("fork is different with vfork \n");
13
14 pid = fork();
15 if(!pid){
16 i=3;
17 while(i-- > 0){
18 printf("Child process is running\n");
19 globVar++;
20 var++;
21 sleep(1);
22 }
3 printf("Child's globVar = %d,var = %d\n",globVar,var);
24 }
25 else if(pid){
26 i=5;
27 while(i-- > 0){
28 printf("Parent process is running\n");
29 globVar++;
30 var++;
31 sleep(1);
32 }
33 printf("Parent's globVar = %d,var %d\n",globVar,var);
34 exit(0);
35 }
36 else{
37 perror("Process creation failed\n");
38 exit(-1);
39 }
40 }
程序经过调试后;
think@ubuntu:~/work/process_thread/fork3$ ./fork
fork is different with vfork
Parent process is running
Child process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child's globVar = 8,var = 4
Parent process is running
Parent process is running
Parent's globVar = 10,var = 6
函数实例2:用vfork()函数创建一个进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globVar = 5;
6 int main(void)
7 {
8 pid_t pid;
9 int var = 1;
10 int i;
11
12 printf("fork is different with vfork!\n");
13
14 pid = vfork();
15 if(!pid){
16 i=3;
17 while(i-- > 0)
18 {
19 printf("Child process is running\n");
20 globVar++;
21 var++;
22 sleep(1);
23 }
24 printf("Child's globVar = %d,var =%d\n",globVar,var);
25 }
26 else if(pid){
27 i = 5;
28 while(i-- > 0)
29 {
30 printf("Parent process is running\n");
31 globVar++;
32 var++;
33 sleep(1);
34 }
35 printf("Parent's globVar = %d,var %d\n",globVar,var);
36 exit(0);
37 }
38 else {
39 perror("Process creation failed\n");
40 exit(0);
41 }
42
43 }
程序经过调试后结果为:
think@ubuntu:~/work/process_thread/fork3$ ./vfork
fork is different with vfork!
Child process is running
Child process is running
Child process is running
Child's globVar = 8,var = 4
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent's globVar = 13,var = 5
Tiger-John说明:
我们通过以上两个函数的执行可以看到一些区别:
1. 使用 fork 创建子进程时,子进程继承了父进程的全局变量和局部变量。在子进程中,最后全局变量 globVar 和局部变量 var 的值均递增 3 ,分别为 8 和 4. 不管是全局变量还是局部变量,子进程与父进程对它们的修改互不影响。
2. 父进程中两者分别递增 5. 最后结果为 10 和 6
通过以上程序的运行结果可以证明 fork 的子进程有自己独立的地址空间。
3. 子进程和父进程的执行顺序是很随意的,没有固定的顺序。父子进程的输出是混杂在一起的。
--------------------------------------------
1.用vfork()函数创建子进程后,父进程中globVar和var最后均递增了8.这是因为vfork的子进程共享父进程的地址空间,子进程修改变量对父进程是可见的。
2.使用vfork()函数子进程后打印的结果是子进程在前,父进程在后,说明vfork()保证子进程先执行,在子进程调用exit获exec之前父进程处于阻塞等待状态。
3>那么现在来看看fork()和vfork()函数之间的区别:
(1)fork():使用fork()创建一个子进程时,子进程只是完全复制父进程的资源。这样得到的子进程独立于父进程具有良好的并发性。
vfork(): 使用 vfor创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程。而是子进程共享父进程的地址空间,即子进程完全运行在父进程的地址空间上。子进程对该地址空间中任何数据的修改同样为父进程所见。
(2)fork():父子进程执行顺序不定;
vfork():保证子进程先运行,在调用exec或exit之前与父进程共享数据,在它调用exec或exit之后父进程才可能被调度运行。
(3)vfork保证子进程先运行,在它调用exec或exit后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。