Linux进程,fork-专研精讲(实例讲解)!!!

创建进程有两种方式,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应不应该执行!!!

如有问题:欢迎留言!!!

时间: 2024-08-09 11:10:17

Linux进程,fork-专研精讲(实例讲解)!!!的相关文章

不看绝对后悔的Linux三剑客之grep实战精讲

三.Linux三剑客之grep命令精讲 [命令简介]Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来.grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户. [功能说明]grep***** ==擅长过滤器,把想要的或者不想要的分离开.Linux三剑客 老三. [用法格式]grep [选项]... PATTERN [FILE]... [参数选项][options]主要参数

不看绝对后悔的Linux三剑客之awk实战精讲

一.Linux三剑客之awk命令精讲 第1章 awk基础入门 1.1 awk简介 awk不仅仅时linux系统中的一个命令,而且是一种编程语言,可以用来处理数据和生成报告(excel).处理的数据可以是一个或多个文件,可以是来自标准输入,也可以通过管道获取标准输入,awk可以在命令行上直接编辑命令进行操作,也可以编写成awk程序来进行更为复杂的运用.本章主要讲解awk命令的运用 1.2 awk环境简介 [[email protected] ~]# cat /etc/redhat-release 

不看绝对后悔的Linux三剑客之sed实战精讲

二.Linux三剑客之sed命令精讲 1,前言 我们都知道,在Linux中一切皆文件,比如配置文件,日志文件,启动文件等等.如果我们相对这些文件进行一些编辑查询等操作时,我们可能会想到一些vi,vim,cat,more等命令.但是这些命令效率不高,这就好比一块空地准备搭建房子,请了10个师傅拿着铁锹挖地基,花了一个月的时间才挖完,而另外一块空地则请了个挖土机,三下五除二就搞定了,这就是效率.而在linux中的"挖土机"有三种型号:顶配awk,中配sed,标配grep.使用这些工具,我们

Linux进程fork,exec,vfork详解

在Unix/Linux系统下进程创建时需要进行如下系统调用:fork/exec fork()把一个进程复制成二个进程:parent (old PID), child (new PID) exec()用新程序来重写当前进程:PID没有改变 接下来就重点学习这两个系统调用: 当我们fork() 创建一个继承的子进程将会发生如下事情:复制父进程的所有变量和内存,复制父进程的所有CPU寄存器(有一个寄存器例外)(这个寄存器是用来区分父进程和子进程的PID) fork()的返回值:调用fork()函数成功

《Linux系统精讲》学习总结(二)

本次总结我将采取总结性,对比性的方式一目了然的展示出来,首先将所有的命令总结在一起,然后对比性的总结部分知识点,最后谈谈本周的学习体会. 一.Linux系统精讲常用命令 格式:命令作用 命令 :命令语法:#注释说明或者补充 例如:创建目录 mkdir:mkdir -m/p 目录名称: 创建目录 mkdir:mkdir -m/p 目录名称: 删除目录 rmdir:rmdir -m/p 目录名称: rm:  rm -r 目录名称= rmdir;  #系统会询问是否删除: rm -f 目录名称:  

Linux高频命令精讲(三)

[教程主题]:2.Linux高频命令精讲 [2.1]Linux的运行方式 图形运行方式 - 本地使用KDE/Gnome集成环境 - 运行X Server远程使用图形环境 命令行(字符运行)方式 - 本地虚拟终端 - 使用Telnet远程登录 - 使用SSH远程登录 [2.2]字符界面的使用 为什么要使用字符工作方式 - 可以高效率的完成任务 - 远程登录操作均使用字符界面 - 节省系统资源 进入字符模式的方法 - 图形环境下,右键桌面  —> 终端 - 系统启动后直接进入字符模式 - 远程tel

基于MFC设计的MINI快跑游戏(游戏开发、MFC精讲、线程与进程通信)

课程讲师:今夜有风    课程分类:游戏开发    适合人群:初级    课时数量:36课时    更新程度:完成    服务类型:C类(普通服务类课程)    用到技术:MFC    涉及项目:MINI快跑游戏 给各位网友分享一套课程,有兴趣的可以加我 2748165793 一.项目简单介绍 MFC简介: MFC实际上是微软提供的,用于在C++环境下编写应用程序的一个框架和引擎.VC++是Windows下开发人员使用的专业C++ SDK(SDK,Standard SoftWare Develo

linux中fork()函数具体解释(原创!!实例解说)

 一.fork入门知识 一个进程,包含代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程差点儿全然同样的进程,也就是两个进程能够做全然同样的事,但假设初始參数或者传入的变量不同,两个进程也能够做不同的事.    一个进程调用fork()函数后,系统先给新的进程分配资源,比如存储数据和代码的空间.然后把原来的进程的全部值都拷贝到新的新进程中,仅仅有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个样例: /* * fork_test.c * version

linux中fork()函数详解(原创!!实例讲解) (转载)

 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都 复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个例子: /* * fork_test.c * version 1 *