赞
踩
创建进程有两种方式,1:由操作系统创建;2:由父进程创建
由操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系(称之为:系统进程)。而对于由父进程创建的进程(子进程),它们和父进程之间是隶属的关系,然后子进程继续创建属于自己的子进程,形成进程家族,子进程可以继承其父进程几乎所有资源
系统调用fork是创建一个新进程的唯一方法
pid_t(类型探究)(参考博客:http://blog.chinaunix.net/uid-20753645-id-1877915.html)
1,/usr/include/sys/types.h中有如下定义
- #ifndef __pid_t_defined
- typedef __pid_t pid_t;
- # define __pid_t_defined
- #endif
(
pid_t 其实就是__pid_t类型)
2,/usr/include/bits/types.h中可以看到这样的定义
- /* We want __extension__ before typedef's that use nonstandard base types
- such as `long long' in C89 mode. */
- # define __STD_TYPE __extension__ typedef
- #elif __WORDSIZE == 64
- # define __SQUAD_TYPE long int
- # define __UQUAD_TYPE unsigned long int
-
- ....................
- __STD_TYPE __PID_T_TYPE __pid_t; /* Type of process identifications. */
- __STD_TYPE __FSID_T_TYPE __fsid_t; /* Type of file system IDs. */
- __STD_TYPE __CLOCK_T_TYPE __clock_t; /* Type of CPU usage counts. */
- __STD_TYPE __RLIM_T_TYPE __rlim_t; /* Type for resource measurement. */
这里我们要注意的是:__extension__ typedef(关于__extension__的作用:gcc对标准C语言进行了扩展,但用到这些扩展功能时,编译器会提出警告,使用__extension__关键字会告诉gcc不要提出警告,所以说:相当于typedef)
(__PID_T_TYPE也就是__pid_t)
3,/usr/include/bits/typesizes.h中可以看到这样的定义
- #define __OFF64_T_TYPE __SQUAD_TYPE
- #define __PID_T_TYPE __S32_TYPE
- #define __RLIM_T_TYPE __SYSCALL_ULONG_TYPE
(_S32_TYPE也就是__PID_T_TYPE)
4,/usr/include/bits/types.h中我们终于找到了这样的定义
- #define __U16_TYPE unsigned short int
- #define __S32_TYPE int
- #define __U32_TYPE unsigned int
到了这里,我们终于找到了定义,原来:pid_t就是int类型的了
pid: 调用fork函数的返回值,(0:子进程的运行)(-1:进程创建失败)(其他:一般为子进程的标识符)
getpid:(当前进程的标识符)
getpid() returns the process ID of the calling process. (This is often used by routines that generate unique temporary filenames.)
getppid:(当前进程的父进程的标识符)
getppid() returns the process ID of the parent of the calling process.
程序1:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
-
- int main()
- {
- pid_t pid;
-
- printf(" %d %d %d\n", pid,getpid(), getppid());
- printf("Process Creation Study!\n");
- pid = fork();
- switch(pid)
- {
- case 0:
- printf("Child process is running , retuernpid is %d,curentpid is %d, Parent is %d\n",pid, getpid(), getppid());
- break;
- case -1:
- printf("Process creation failed\n");
- break;
- default:
- printf("Parent process is running , returnpid is %d,curentpid is %d, parentpid is %d\n",pid ,getpid(), getppid());
- }
-
- exit(0);
- }

运行结果:
从运行结果可以看到:
PID:0 getpid():5581 getppid():5557
说明:当前这个程序(main函数)调入内存变成进程,应该是由其它的进程(进程号为5557的那个进程,应该是调用fork函数,产生的子进程(进程号为5581),fork返回为0)
这里查看一下进程:ps -A
。。。。。
可以知道那个进程号为5557的进程就是bash(一般来说,安装linux时,如果没有改shell,默认的都是bash,所以我们开启一个终端就会生成一个叫做bash的进程,再打开一个终端又会生成一个bash进程,关掉一个终端就会少一个bash进程,但是终端并不是bash,终端是一个界面,但它调用bash)
这里我们来看一下进程树:pstree
我现在开了四个终端
接着看:
下面程序执行了:
父进程:returnpid:5582 current:5581 getppid:5557
(主函数中的fork返回5582,当前的进程号为5581,父进程为5557,也就是上面提到的bash进程)
子进程:returnpid:0 current:5582 getppid:5581
(fork函数返回0,当前是子进程在执行,进程号为5582,父进程为5581)
关于执行顺序的探究:
程序2:
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- int main()
- {
- pid_t pid;
- char *msg = NULL;
- int k;
-
- printf("process creation study\n");
- pid = fork();
- switch(pid)
- {
- case 0:
- msg = "child process is running";
- k = 3;
- break;
- case -1:
- perror("process creation failed\n");
- break;
- default:
- msg = "parent process is running";
- k = 5;
- break;
- }
-
- while(k > 0)
- {
- puts(msg);
- sleep(1);
- k--;
- }
-
- exit(0);
- }

运行结果:
这是没有调用sleep的程序
- while(k > 0)
- {
- puts(msg);
- // sleep(1); //这是去掉sleep的情况
- k--;
- }
可以很容易的看到,只有在父进程彻底执行完之后才会执行子进程
然后我们很容易很自然的总结吗???
每次一开始,对于我当前的系统来说:总是父进程先执行的,那么是不是说明:内核所使用的调度算法中,我的父进程优先于子进程呢??
当输出多条语句的话,总是父进程先执行完之后,子进程才会执行的,如果存在sleep的话,相当于中断,将当前系统的控制权交出去了,而子进程此刻也在等待着,所以子进程执行,同理,父进程执行,这样形成的交替吗???
上面这些真的对吗,其实,一开始我真的是这样想的,哈哈,但是经过验证之后,并不是这样的
1,对于上面的程序而言(去掉sleep语句),当多运行几次之后,我们会发现,并不是所有的运行结果都是和上面所
想的一样,有的运行结果是父进程还没有执行完,子进程就开始执行了,所以说:上面的解释不同
2,对于一开始,父进程总是先执行,这也是有原因的:因为对于进程的创建而言,这是特别麻烦的一件事(给一块存
储空间,复制父进程的大部分资源,代码段,数据段,创建id号,等等)形成子进程,我们不要忘了,子进程的形
成需要很多的时间,所以在这个时间里父进程开始执行的,所以我们总是会直观的看到父进程总是先执行的
3,那么如何理解:一般来说:fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法,这句话的呢???
其实,这句话是完全正确的,只是我们曲解了它的意思,它的意思是在父子进程彻底,完全的创建之后,父进程还是子进程的执行顺序是不确定的,上面没有sleep,父进程先打印完自己的五条语句,说明了:打印完5条语句特别快,比子进程彻底创建完成还要快,所以父进程的先打印完成
当时对于很多条的打印,我们可以看看:
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <unistd.h>
-
- int main()
- {
- pid_t pid;
- char *msg = NULL;
- int k;
-
- printf("process creation study\n");
- pid = fork();
- switch(pid)
- {
- case 0:
- msg = "child process is running";
- k = 100;
- break;
- case -1:
- perror("process creation failed\n");
- break;
- default:
- msg = "parent process is running";
- k = 100;
- break;
- }
-
- while(k > 0)
- {
- puts(msg);
- k--;
- }
-
- exit(0);
- }

可以看到,程序中,没有sleep语句,只是这时候,我们将程序中的循环变量k改为了100:
运行结果:
这个东西只是我们的部分截图,但是也可以很明显的看到父子进程在交替运行,也就是在抢占资源了
程序3(printf输出问题)
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
-
- int main()
- {
- printf("today is good!");
- pid_t pid;
- pid = fork();
- if(pid == 0)
- {
- printf("child process is running\n");
- }
- else if(pid != -1)
- {
- printf("parent process is running\n");
- }
-
- return 0;
- }

运行结果:
当程序为这样时:
printf("today is good!\n");
程序的唯一区别就是:printf的输出有没有(\n)换行符,两者为什么输出的差距这么大呢,一个输出一次,一个输出两次
后来查了查,这个跟printf的缓冲机制有关(LINUX和Window下的不一样),linux下:printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列中,并没有实际的写到屏幕上,但是,只要满足printf的刷新条件(缓冲区填满
,写入的字符中有‘\n’, '\r', '\t',调用fflush手动刷新缓冲区,调用scanf要从缓冲区中读取数据时),就会立即刷新,马上就会打印了,对于当前的程序而言:
运行了printf(“today is good!”),字符串内容被放到了缓冲里,程序运行到fork时,缓冲里的内容也被子进程复制过去了。因此在子进程stdout缓存里面也就有了相应的字符串,所以我们会看到最终的被打印了两遍!!!
而一旦后面加上了换行,那么缓冲里面的字符串直接就被刷新了,就不会有相应的字符被复制到子进程了的!!!
那么从fork之后,父子进程到底共享了那些东西?设置全局变量,静态变量,局部变量到底会不会在父子进程中改变呢???
程序3(探究全局变量,静态变量,局部变量)
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
-
- int a = 0; //全局变量,判断共享
- static int b = 0; //静态全局变量
-
- int main()
- {
- int c = 0; //局部变量
- pid_t pid;
- pid = fork();
-
- if(pid == 0)
- {
- printf("child process is running\n");
- a = a + 2; b = b + 2;
- c = c + 4;
- }
- else if(pid != -1)
- {
- printf("parent process is running\n");
- a = a + 3; b = b + 3;
- c = c + 5;
- }
-
- printf("全局变量a is :%d\t 静态变量b is :%d\n", a, b);
- printf("局部变量c is :%d\n", c);
- return 0;
- }

运行结果:
可以很容易看到对于全局变量,静态变量,局部变量,在父子进程并不是共享的
关于其他继承的关系:man fork命令
- fork() creates a new process by duplicating the calling process. The new process, referred to as the child, is an exact duplicate of
- the calling process, referred to as the parent, except for the following points:
-
- * The child has its own unique process ID, and this PID does not match the ID of any existing process group (setpgid(2)).
-
- * The child's parent process ID is the same as the parent's process ID.
-
- * The child does not inherit its parent's memory locks (mlock(2), mlockall(2)).
-
- * Process resource utilizations (getrusage(2)) and CPU time counters (times(2)) are reset to zero in the child.
-
- * The child's set of pending signals is initially empty (sigpending(2)).
-
- * The child does not inherit semaphore adjustments from its parent (semop(2)).
-
- * The child does not inherit record locks from its parent (fcntl(2)).
-
- * The child does not inherit timers from its parent (setitimer(2), alarm(2), timer_create(2)).
-
- * The child does not inherit outstanding asynchronous I/O operations from its parent (aio_read(3), aio_write(3)), nor does it
- inherit any asynchronous I/O contexts from its parent (see io_setup(2)).
-
- The process attributes in the preceding list are all specified in POSIX.1-2001. The parent and child also differ with respect to the
- following Linux-specific process attributes:
- * The child does not inherit directory change notifications (dnotify) from its parent (see the description of F_NOTIFY in fcntl(2)).
-
- * The prctl(2) PR_SET_PDEATHSIG setting is reset so that the child does not receive a signal when its parent terminates.
-
- * The default timer slack value is set to the parent's current timer slack value. See the description of PR_SET_TIMERSLACK in
- prctl(2).
-
- * Memory mappings that have been marked with the madvise(2) MADV_DONTFORK flag are not inherited across a fork().
-
- * The termination signal of the child is always SIGCHLD (see clone(2)).
-
- * The port access permission bits set by ioperm(2) are not inherited by the child; the child must turn on any bits that it requires
- using ioperm(2).
- Note the following further points:
- * The child process is created with a single thread—the one that called fork(). The entire virtual address space of the parent is
- replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of
- pthread_atfork(3) may be helpful for dealing with problems that this can cause.
- * The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open
- file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two descriptors share open
- file status flags, current file offset, and signal-driven I/O attributes (see the description of F_SETOWN and F_SETSIG in
- fcntl(2)).
- * The child inherits copies of the parent's set of open message queue descriptors (see mq_overview(7)). Each descriptor in the
- child refers to the same open message queue description as the corresponding descriptor in the parent. This means that the two
- descriptors share the same flags (mq_flags).
- * The child inherits copies of the parent's set of open directory streams (see opendir(3)). POSIX.1-2001 says that the correspond‐
- ing directory streams in the parent and child may share the directory stream positioning; on Linux/glibc they do not.

程序4(文件指针的探究):
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
-
- int main()
- {
- printf("today is good\n");
- pid_t pid;
- pid = fork();
- printf("tomarrow is also good\n");
- if(pid == 0)
- {
- printf("child process is running\n");
- }
- else if(pid != -1)
- {
- printf("parent process is running\n");
- }
-
- return 0;
- }

运行结果:
可以看到:today is good打印了一遍,tomorrow is also good语句打印了两遍,说明子进程基本上复制了父进程,包括文件指针,但是文件指针指向了fork函数那个位置(所以today只执行一遍),所以两个进程都从那个位置开始执行,所以才会将tomorrow打印两遍啊
程序5:(关于孤儿进程)
- include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
-
-
- int main()
- {
- pid_t pid;
-
- pid = fork();
- switch(pid)
- {
- case 0:
- printf("child process is running, getpid :%d getppid:%d\n", getpid(), getppid());
- sleep(1);
- printf("child process is running, getpid :%d getppid:%d\n", getpid(), getppid());
- break;
- case -1:
- perror("process creation failed\n");
- exit(-1);
- default:
- printf("parent process is running , getpid:%d getppid:%d\n", getpid(), getppid());
- exit(0);
- }
- return 0;
- }

运行结果:
可以看到:对于子进程的两次打印来看,第一次的时候,说明当前这个进程是有父进程的,而sleep完之后,再次打印,说明这个进程已经没有父进程了,是个孤儿进程了,有人可能会问???我知道,是init这个进程,但是它的id号不是1吗??这里的是1803,所以应该不是孤儿进程,,,,
是吗???,这里我们可以再来看看
ps -A(命令)
哈哈,这回没有问题了吧!!!
当然,fork也可能会出错:
创建进程失败返回-1:
1,父进程拥有的子进程的个数超过了规定的限制,此时errno值为:EAGAIN
2,可供使用的内存不足导致进程创建失败,此时errno值为:ENOMEM
程序4:连续调用fork函数
- #include <unistd.h>
- #include <stdio.h>
-
- int main()
- {
- int i = 0;
- pid_t pid;
- for(i = 0; i < 2 ; i++)
- {
- pid = fork();
- if(pid == 0)
- printf("%d child pid:%d getpid:%d getppid:%d\n" ,i , pid, getpid(), getppid());
- else if(pid == -1)
- printf("the create process is fail\n");
- else
-
- printf("%d parent pid:%d getpid:%d getppid:%d\n" ,i , pid, getpid(), getppid());
- }
- }

运行结果:
我们来仔细分析一下:
当前的程序,我们称之为main进程
1,首先在for循环中,i = 0;第一次执行fork(这里会产生一个子进程)
输出结果(第一行):parent,pid为3750,说明此刻执行的是fork之后的父进程(main进程),3750为新产生的子
进程的pid,当前main进程的pid为3749,3052为main进程的父进程(bash进程)
2,for循环中,i = 1;第二次执行fork(这里还是会产生一个子进程)
输出结果(第二行):同上,此刻再次新产生了一个子进程, pid = 3751
3,输出结果(第三行):0, child,pid:0,(getpid:3750)说明这个是第一次fork执行时产生的子进程,
getppid:3749,也就是 说:它是main进程的第一个子进程
4,输出结果(第四行):1, child,pid:0,(getpid:3751)说明这个是第二次fork执行时产生的子进程,
getppid:3749,也就是说:它是main进程的第二个子进程
5,这是第三次执行fork函数(是第一次产生的fork子进程,进入for循环后,i = 1,调用fork的结果)
输出结果(第五行):1, parent,pid:3752,(getpid:3750),说明当前的进程是main进程产生的第一个进
程,然后,产生了一个新的进程,进程号为:3752
6,这是第三次执行fork,返回的子进程的内容
输出结果(第六行):1, child , pid:0,(getpid:3752),是第三次fork后的子进程,也是main进程产生的子进程的子进程
大家可能会注意到,上面的程序中
第三次fork之后,也就是main进程的子进程调用fork后
对于当前进程(main进程的子进程),它的getppid:为1803,可能会想不通,这是因为,此刻main进程已经结束了,当前这个进程已经变成了孤儿进程了
草图:
或者我能还能发现创建进程pid就如同链表一般:3052--》3749--》3750--》3751--》3752
- int main()
- {
- int i = 0;
- pid_t pid;
- for(i = 0; i < 3; i++)
- {
- pid = fork();
- if(pid == 0)
- printf("son\n");
- else
- printf("father\n");
- }
- return 0;
- }
运行结果:
所以,是这样子的:
对于这种N次循环的情况,执行printf函数的次数为2*(1+2+4+……+2N-1)次,创建的子进程数为1+2+4+……+2N-1个
最后,我们再来看看最后一个程序(问总共多少个进程):
- #include <unistd.h>
- #include <stdio.h>
-
- int main()
- {
- fork();
- fork() && fork() || fork();
- fork();
-
- return 0;
- }
1,首先注意到的是 : && ||
A && B:如果A为0的话,那么就不会探求B的真假了
A || B:如果A为1,就没有必要执行后面的了,因为整条语句的真假,我们已经知道了
这里主要的意思是:根据fork的返回值来决定后续的fork是否执行
恩,对,如果不算main进程的话,应该是15个,总共是16个,主要的也就是清楚当前的fork应不应该执行!!!
如有问题:欢迎留言!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。