第七章 进程控制及操作(一)

经历了上一周悲剧的学习,这一周,我决定让自己充实起来,

而进程控制(Peocess_Control)的学习尤为关键

所以,下面即为我的收获,本章的学习分为两部分

第一部分是进程的了解,以及相关操作

第二部分就是进程的一些其他操作,以及项目的一些实现要点


一,进程
  • 进程的概念

了解什么是进程,进程的概念以及特点.

进程的概念总结后有以下几点:(进程与程序,线程的区别)

  1. 进程是一个动态的实体,是操作系统分配资源的基本单位

  2. 进程与程序的区别在于:

    程序只是代码块,而进程是将代码移至内存中,之后为其分配资源以及空间运行中的程序,

    可以这样理解进程,即特点之一是动态的

  3. 进程与线程:

    为了使计算机能够在同一时间执行更多的任务,又在进程中划分了多个线程,

    线程是操作系统所能操作控制的最 小单位,

    进程分配有资源和内存,对于这些内容,线程不单独享用,多个线程共享这些资源以及内存,

    同一个进程可以创建和撤销多个线程,

    多个线程可以在进程中并行进行.

以上即为进程的基本概念.

其实我们可以从计算机的发展过程来看:

1.进程最早是分时操作系统(一个计算机史上的分支方向)的基本单位

2.后来是面向进程编程的操作系统 (例如 UNIX, Linux Kernel < 2.6)

3.直到现在,是以面向线程编程的操作系统.进程只是线程的容器, (现代大多数操作系统,Linux Kernel>2.6)

进程(Peocess)一直是十分重要的概念,可以说进程是计算机对计算机资源的调度方式

这样才能使计算机能够执行任务.


  • 关于进程的特点:

动态性:进程是动态产生的,也是动态消亡的

并发性:多个进程可以并行进行

独立性:单个进程可独立进行,系统为其分配资源,是计算机调度的基本单位

异步性:进程以不可预知的方向前进,速度,耗时,都是不可预知的


  • 进程标识符(process ID)_PID

进程是计算机操作系统资源分配的基本单位,那么,

计算机中同时进行的可能会有成百上千的进程,

所以对进程最直观的区分就是进行编号,即进程ID Process_ID

关于进程标识符:

所有进程标识符都是惟一的

所有进程标识符非负

所有的PID,在进程开始时,为其分配PID,之后对其进行操作,进程结束后,PID回收

Linux操作系统中提供了用于获取PID的相关函数,如下:

头文件:

#include
#include
函数声明 功能
pid_t getpid( ) 获取到当前进程ID
pid_t getppid( ) 获取当前进程父进程ID
Pid_t getuid( ) 获取当前进程实际用户ID
pid_t geteuid( ) 获取当前进程有效用户ID
pid_t getgid( ) 获取当前进程实际用户组ID
pid_t getegid( ) 获取当前进程有效用户组ID

既然提到进程ID,就不得不进行细分:

1.RUID 实际用户ID:进程的执行者ID,仅只有root可以进行修改

2.EUID 有效用户ID:进程需要执行的用户ID,可以理解为权限ID,即该进程的拥有者

3.SUID 保留设置用户ID:进程切换其有效用户(EUID)时使用

对于这三种用户ID,有这些需要注意的点:

1.一般用户可以将EUID设置为RUID,SUID其一,root可以将EUID设置为任意合法的UID

2.当未设置SUID时,SUID = RUID,当设置SUID后,SUID = EUID

root将S,E,RUID进行修改,修改他们的值.一般用户,仅当UID = R/SUID时,可将EUID置为UID,且不会修改值

  • 进程的结构

之前上面也提到过,进程时动态的实体,既然是实体,那么他必然有其既定的数据结构

如图:
第七章 进程控制及操作(一)_第1张图片

代码段:即是这个进程的代码

数据段:保存的是,进程中的全局变量,常量,静态变量

堆栈段:栈用于函数调用,其中保存着函数的参数,函数的内部定义的局部变量

注意:这三部分实体构成了进程的结构,之后在进程操作中,要特别注意三个段数据的变化

  • 进程的状态

运行状态:R,实际上,运行状态与就绪状态,统称可运行状态,R表示

可中断的等待状态:S,现在可中断的等待状态,称为可终端的睡眠状态,可以被其他程序唤醒

不可中断的等待(睡眠)状态:D,不可被唤醒,直至等待事件完成

僵死状态:Z,已终止,描述符仍在,直至父进程调用wait后才能释放

停止状态:T,表示进程暂停,或者正在被信号跟踪(gdb调试)

同时使用后缀字符进行状态的补全:

< :高优先级程序

N :低优先级进程

L :内存锁页,此页不可以被换出内存

s :该进程为会话首进程

l :多线程进程

+ :进程位于前台进程组

既然上面提到了这么多的进程状态,我们就来看看怎么查看进程状态:

ps  查看进程的基础命令

常用选项:

-A,显示所有进程,

-u显示指定用户进程

-ef 显示进程信息,搭配命令行(可配合管道使用)

常用:

ps -aux 即可成功查看,当前计算机上所有进程

如图:

  • 进程的内存映像

说了这么多,都是基于软件上的进程概念,那么,实质上进程是如何反映在内存中的呢?

一,将程序拷贝至内存中,使程序变成进程,之后,就是进程在内存中的映像

二,进程在内存中的映像

何为映像?

我的理解就是,映像,即为进程在内存中的实际反映,即是,进程在内存中是如何编排的

首先,来看两张图(图片引自网络)
第七章 进程控制及操作(一)_第2张图片

第七章 进程控制及操作(一)_第3张图片

从内存的低地址开始,依次填入进程结构块中的内容

通过了解内存映像,可以加深对于进程概念的理解:

可执行程序没有堆栈,存储在硬盘中;进程存储在内存中,有堆栈段

可执行程序存储在硬盘,是静态的,不变的.而进程储存在内存中,是变化的,动态的.


二,进程控制

了解了进程之后,进程控制则是重点内容

进程控制,主要有以下方面:

  • 创建进程

  • 创建守护进程(后台)

  • 退出进程

  • 执行新程序

  • 等待进程结束

1.创建进程

进程很重要,那么重中之重就是进程的创建了,因为只有创建了进程,才能有后续的进程操作.

在UNIX/Linux中进程的创建只有两种类型:

由父进程创建出新的子进程

由操作系统内核创建出的一些特殊进程,比如,init进程,0,1号进程

而我们平时进行进程创建的多是第一种类型,即从父进程重新创建出新的子进程

来理解进程的生命过程:

开始 -> 调用函数创建进程 -> 进程执行 -> 进程终止 -> 传递信息给父进程 -> 父进程进行资源的回收 -> 结束

而进程在创建时,则是:

开始->为进程分配惟一的PID->分配数据段,堆栈段

首先介绍一个函数,fork( )函数,这是UNIX/Linux中特有的函数,不同于Win下的CreatProcess( )

#incldue
#include
pid_t fork(void);

fork在英语中的语义是”分叉,分支”,在程序设计里则是创建新进程的不二法宝.

fork( )一个新进程,主要有两点作用:

1.使用多进程执行一个程序(通常用处不大)

2.fork( )之后,立即调用exec函数族,执行另一个程序(常用法)

现在来理解一下fork( )的实际过程:

调用fork( )之后,操作系统立马新建一个进程在内存中,称为子进程.

子进程与父进程共享代码段,其数据段以及堆栈段为父进程的拷贝,即,调用fork( )之后,

创建了两个几乎完全相同的进程,而两个进程的关系,更像是兄弟关系.

fork进行进程的创建,当进程fork( )创建完后,

fork( )这个特殊的函数会返回两个返回值

来画一张图理解:

第七章 进程控制及操作(一)_第4张图片

如上图,进程fork后,新建了一个子进程,

两个进程并行运行,互相各走各的,

所以希望大家注意一个误区,现在是两个进程在执行fork之后的相同代码

所以才会有不同输出结果! ! !

看下面这段程序:

#include
#include
#include
int main(int argc,char **argv)
{
   pid_t pid;
  /* printf("Fork start!");*/            /*子进程对缓冲区的数据实行了拷贝,虽然fork( )在printf之后*/
   printf("Fork start!\n");
   pid = fork( );

   switch(pid)
   {
      case 0:
         printf("I'm child process! my pid is %d,my father pid is%d\n",getpid( ),getppid( ));
         break;
      case -1:
         printf("Error!");
         break;
      default:
         sleep(1);
         printf("I'm parent process!my pid is %d,my parent pid is%d\n",getpid( ),getppid( ));
         break;
   }

   return 0;
}


由其进程ID,可以看出其父子进程关系,

比較有意思的是:

Fork start!,打印了一次

但是如果,是使用了上面的那條語句,則會打印两次Fork start!

这是为什么?

这就要提到printf的机制了,他先将内容输出到缓冲区中,然后等待缓冲区刷新进行内容的输出

但是,如果缓冲区没有刷新,也就是说,子进程会将缓冲区拷贝,然后进行输出,在最后程序结束时

可以总结一下,缓冲区刷新的条件:

1.缓冲区满溢出

2.\n ,\t,\v等强制刷新化冲区

3.程序结束时,不管是使用return还是exit,都会清理I/O输出,将缓冲区的内容输出,(默认return,接下来一个例子证明)

再来分析一下上面的程序:

pid获取fork( )的返回值,fork( )之后是两个进程分别进行fork( )后面的代码

swich语句中,

父进程获取到的pid是fork第一个返回值–子进程的PID,都是非0的

子进程获取到的pid是fork第二个返回值–0,因为子进程无子进程

如果fork( )失败,则会返回-1

fork的返回值很特殊,在进行程序设计时,需要明辨各自的返回值

那么,fork( )的实质清楚了,那么父子进程的运行情况又是什么样的呢?

看代码:


#include
#include
#include
int main(int argc,char **argv)
{
   pid_t pid;
   int k;
   char *s;
  /* printf("Fork start!");*/   //同上例的缓冲区问题
   printf("Fork start!\n");
   pid = fork( );
   switch(pid)
   {
      case 0:
         s = "I'm child process! my pid is ,my father pid is\n";
         k = 3;
         break;
      case -1:
         printf("Error!");
         break;
      default:
         /*sleep(1);*/
         k = 5;
         s = "I'm parent process!my pid is ,my parent pid is\n";
         break;
   }
   while(k--)
   {
      printf("%s\n%d\n",s,k);
      sleep(1);
   }

   return 0;
}

结果:
第七章 进程控制及操作(一)_第5张图片
其实但从上面结果来看,结果是不甚明显的,可以看到子父进程勉强是交替进行的,

如果把上面的sleep函数去掉,你会发现,其实是一个进程结束后,另一个进程才进行.

这一定就是对的吗?

事实上,在了解了操作系统的相关内容后,会发现,

父子进程其实是杂乱无章的,而且是随机的,你所看到的规律是因为试验结果比较小导致的

真正决定子父进程顺序的其实是操作系统的调度算法,以及时间片的分配

进程具有异步性,反映出来就是父子进程是随机进行的

而且,需要给大家补充的一个概念,就是计算机同一时间只能处理单进程,多线程

之所以我们平时可以看到计算机同时进行多个任务,这是因为计算机切换进程的速度太快,约1/60s


既然上面提到了,父子进程是随机的,主要依靠计算机CPU的调度算法,现在来了解两个特殊的进程:

1.孤儿进程

既然父子进程有异步性,不同时开始,不同时结束.那么,一旦父进程先于子进程结束,

则子进程称为“孤儿进程”

因为此时该子进程失去了其父进程,而在UNIX类操作系统中,这样的事是不被允许的,一个子进程必须有父进程

这个时候,特殊进程,PID == 1的进程,init进程就派上用场了,

init进程会收养该子进程,成为该子进程的父进程,处理之后的一系列动作

来看一段代码

#include
#include
#include
int main(int argc,char **argv)
{
   pid_t pid;                       /*获取PID的参数*/
   printf("Fork start!\n");
   pid = fork( );                   /*创建新进程并获取其PID*/

   switch(pid)
   {
      case 0:
         sleep(1);          /* 此语句用来睡眠,使得子进程后于父进程结束,研究子进程pid的变化*/
         printf("I am child process my pid is %d my parent process pid is %d\n",getpid( ),getppid( ));
         break;
      case -1:
         printf("Fork Error!\n");
         break;
      default:
         printf("I am parent process,my pid is %d, my parent process pid ios %d\n",getpid( ),getppid( ));
   }

   return 0;
}

结果:

注意子进程的ppid(父进程ID),并非上面父进程ID,而是一个奇怪的数字1560,有时是1578,

在/proc中可以看到,里面的编号其实都是PID,15xx,在进入目录,cat status后,会发现

都是名为systemd,1号也是systemd,可以说有多个init进程来接管孤儿进程

你可能会疑惑,为什么对于孤儿进程必须要找一个人来收养,

那么,来看下面一个特殊的进程

2,僵尸进程

上面提到父进程先结束成为孤儿进程,那么子进程先结束便称作僵尸进程

僵尸进程,不要听上去就感觉耸人听闻,其实他更重要的是因为,进程处于”Z+”,僵死状态

现在来解释上面提出的一个问题:为什么一定要找出一个进程来收养孤儿进程

因为,在UNIX类操作系统中,一个进程消亡,并非是真正的死亡,而是会留下一个称为”僵尸进程”的数据结构

因为,UNIX类操作系统使用此机制,可以避免因为异步的特性,子进程先结束,而是父进程失去子进程信息的情况

那么,这也就带来了一个问题,有大量地僵尸进程,则会占用进程标识符PID,最终导致资源泄漏

来看一段代码:

#include
#include
#include
#include
int main(void)
{
   pid_t pid;
   int i = 5;

   pid = fork( );
   switch(pid)
   {
      case 0:
         printf("I am child process my pid is %d,i = %d\n",getpid( ),i++);
         _exit(0);
         break;
      case -1:
         printf("Fork Error!");
         break;
      default:
         sleep(5);
         printf("I am parent process my pid is %d,i = %d\n",getpid( ),i++);

   }

   return 0;
}

结果:
第七章 进程控制及操作(一)_第6张图片
其中Z+即为子进程,僵尸进程

至于为什么需要进程来收养孤儿进程 就是防止不可回收的僵尸进程

僵尸进程大量存在,会严重危害计算机的工作效率

来看看集中解决僵尸进程的办法:

1> 父进程调用wait或者waitpid函数等待子进程的结束

2> 使用信号SIGCHLD,用忽略信号,使得父进程忽略信号,子进程直接移交内核处理

3> 小技巧:kill “僵尸爸爸”,因为一般情况下.僵尸进程很难被杀死

4> 小技巧:连续fork( )两次,并杀死第一次的子进程,之后使用被收养的孤儿进程exec族函数调用新程序

这里,需要给大家明确一个概念:僵尸进程,是很正常的一种情况

是进程结束的必经过程,不过有的进程在进行合理处理后,僵尸进程的时间很短暂,但还是经历了Z+状态

而且,不要将僵尸进程理解成有害而无利的,僵尸进程是因为父进程可能需要获取子进程信息而存在的

在最后,切记一点,在进行僵尸进程的测试时,ps -aux命令需要在另一个终端中使用(个中原因大家自行体会)


vfork( )创建新进程

上面的内容都是以fork( )创建新进程为主,那么vfork( )有什么作用呢?

vfork( )也是创建新进程的函数,同样也是调用一次,有两个返回值

vfork( )和fork( )的区别在于:

1.vfork( )共享代码段,和数据空间,两个进程是共享的,而fork( )只有代码段共享,其他部分都是分离的

2.vfork( )调用后,父进程会等待子进程结束才继续进行,相当于使用了wait/waitpid的fork( )函数

由上面的1.即可知,子进程对于一些全局变量的操作在父进程中也是可见的,因为他们共享,而非拷贝

如果在子进程中依赖父进程的行为,则会造成死锁,(互相等待对方进程的完成)

最后,十分重要的一点,只有调用了exec族函数/exit,才能由子进程回到父进程

来通过代码看结果:

#include
#include
#include
#include
int b = 5;
int main(int argc, char **argv)
{
   pid_t pid;
   int a = 3,i;
   pid = vfork( );                                  /*创建子进程*/
   /*pid = fork( );*/
   switch(pid)                                          /*进行pid判断*/
   {
      case 0:
         i = 3;
         while(i--)
         {
            a++;
            b++;
            sleep(1);
            printf("a = %d,b = %d\n",a,b);
         }
         //_exit(0);                                        /*注意此处应该如何对子进程进行操作*/
         //return;
      case -1:
         printf("Error!");
         break;
      default:
         i = 5;
         while(i--)
         {
            a++;
            b++;
            sleep(1);
            printf("a = %d,b = %d\n",a,b);
         }
         break;
   }

在注释掉case 0结束时的两句话时,结果是这样
第七章 进程控制及操作(一)_第7张图片

在使用return,结果也这样(顺便可以了解,函数结束时若不作处理,默认是return,返回)
第七章 进程控制及操作(一)_第8张图片
这就要讲到,return与exit机制的区别了

return是关键字,而exit是函数

return是函数结尾,用于返回值使用.exit是函数中,用于结束进程使用

尤其是在vfork( )函数中,因为父子进程共享空间

那么,如果使用return,在子进程结束时,会弹栈,破坏公共的堆栈段,等父进程结束时,就会出现错误

可能会返回诡异的栈值,才会出现奇怪的结果,如图一

而使用exit就不会出现这样的结果,exit函数进行进程的结束,并不会破坏公共堆栈段

这就是为什么要使用exit/exec函数族的原因了.

另外,我们再来谈谈exit这个函数

这是个神奇的函数,exit为libc函数,_exit为系统调用Systemcall,exit函数,是封装_exit系统调用而来的

而这两个最大的区别就是,exit会清理I/O输出,即会将所有缓冲区的内容输出,或者写入文件

在main函数中exit(0) ~~ return 0


上面的内容即为创建进程的主要内容了,

在这里我想要分享的是,fork( )函数,vfork( )函数其重要作用都是为了新建子进程后调用exec族函数

执行其他程序的,我们这里是为了了解函数特性才这么做的

虽然vfork( )也是比较好用的,但是现行标准是不建议使用vfork( )的

因为vfork( )共享数据段,堆栈段.操作十分干危险,而且在子进程中影响父进程,在实际开发中是十分危险的

如果,vfork( )之后不是立刻进行exec调用,就要十分小心,谨慎判断

但是现在,fork( )实现了优化,实现了写时拷贝,则使得fork( )之后exec的代价十分小了

所以,现在的建议还是,使用fork( )为好


2.创建守护进程

何为守护进程?简单点讲,就是后台进程,不会因为终端键入的信息而响应,也不会将错误信息发送到屏幕上,

此处,我们对守护进程并不深入展开,仅进行守护进程的创建步骤解析:

1.fork( )创建新进程,并结束父进程,交由init接管

2.setsid( )创建新会话,并使该子进程成为会话组长

3.再次fork( ),创建新的子进程,并且结束父进程

4.关闭不必要的文件描述符,

5.设置掩码为0

6.处理信号

通过以上的步骤即可建立一个后台守护进程.

结果如图:
第七章 进程控制及操作(一)_第9张图片
可以看到,没有反应,表面上,然后可以发现后台有一个进程在执行


3.进程的退出

进程的退出,Linux系统中表示进程的结束,退出的方法分为正常退出和异常退出,主要有以下的方法:

(1)正常退出:

return 进行返回(main中结束进程)

调用exit,_exit,_Exit函数

最后一个线程从启动例程返回

最后一个线程调用pthrad_exit

(2)异常退出:

调用abort函数

接受到信号并终止

最后一个线程对取消请求响应

其中重点注意return,exit的区别,exit,_exit的特色,以上已经讲过,在此不表


4.执行新程序

上面提到过fork( ),vfork( ),实际的重要作用,是创建一个新进程后,立刻进行新程序的调用

而提到新程序的执行,不得不提到的便是exec族函数

通过exec族函数的调用,可以用新的程序来替代当前的进程映像

注意:调用exec族函数并没有新生成进程,调用的同时,原进程”死亡”,其代码段被替换

为其分配新的数据段和堆栈段,惟一相同的就只有PID了,可以说还是同一个进程,但进行的已经是其他程序了

因为exec族函数要调用环境变量,先来了解一下环境变量

通俗点讲,环境变量path,就是在使用程序时,没有告诉其完整路径,系统除了在本目录下寻找以外

还应该去到指定的path路径下去寻找

使用 env命令可以查看当前的环境变量

显示环境变量有两种方式,看代码

第一种,使用在别处定义的全局变量environ

#include
#include

extern char **environ;                  /*在别处定义的系统变量(预定义)*/

int main(int argc,char **argv)
{
   int i;

   for(i = 0;i < argc;i++)
   {
      printf("argv[%d] is %s\n",i,argv[i]);
   }

   for(i = 0;i < argc;i++)
   {
      printf("%s\n",environ[i]);
   }

   return 0;
}

第二种,使用main函数的完整形式,并打印其值envp

#include
#include
int main(int argc,char **argv,char **envp)
{
   int i;

   for(i = 0;i < argc;i++)
   {
      printf("argv[%d] is %s\n",i,argv[i]);
   }

   for(i = 0;i < argc;i++)
   {
      printf("%s\n",envp[i]);
   }

   return 0;
}

使用上面的两种方法都可以获取到环境变量的值.
第七章 进程控制及操作(一)_第10张图片
exec族函数中,execve为惟一的系统调用,其他的函数都会调用此系统调用

第七章 进程控制及操作(一)_第11张图片

第七章 进程控制及操作(一)_第12张图片

exec族函数的详细用法,查看man手册

来看一段exec演示代码

//调用的新程序
#include
#include
#include
int main(void)
{
   printf("HaHa I'm the program to replace the process!\n");
   printf("My pid is %d,my parent pid is %d\n",getpid( ),getppid( ));
   return 0;
}
#include
#include
#include
#include
int main(int argc,char **argv,char **envi)
{
   pid_t pid;
   printf("Exec start!\n");
   pid = fork( );
   if(pid == -1)
   {
      printf("Error!");
      exit(0);
   }
   else if(pid == 0)
   {
      printf("I am child process !\n");
      printf("my pid is %d,my parent pid is %d\n",getpid( ),getppid( ));
      execve("program",argv,envi);
      printf("This sentence won't be print!\n");
      _exit(0);
   }
   else
   {
      sleep(2);
      printf("This is parent process!\n");
   }

   return 0;
}

结果会发现,调用exec族函数后,进程的PID是不会改变的,
第七章 进程控制及操作(一)_第13张图片
其中还有许多项目是保留的:

1.当前工作目录

2.根目录

3.使用的屏蔽字

4.控制终端

5.文件锁


5.等待进程结束

前面提到过,父进程需要等待子进程的结束,可以避免僵尸进程

那么,如何等待进程的结束,调用wait,waitpid函数

#incldue
#include
pid_t wait(int *statloc)
pid_t waitpid(pid_t pid,int *statloc,int options);

wait函数会返回等待进程的PID,如果statloc指向不为空,会将退出码储存在statloc指向的变量中

在这里需要注意的是,退出码是一个字段,并非是单个数字,可以用宏去取出退出码中的信息

举个例子:

WIFEXITED(&statloc)即可取出其中的信息

关于wait函数的宏,详情请查找man手册,

现在介绍waitpid函数,其中pid表示目标子进程的PID,statloc意义相同,options是可以添加的选项

可以看出,waitpid函数yuwait的一个重要区别就是,wait只等待第一个结束的子进程

而waitpid函数,可以等待指定的子进程结束,而且waitpid提供了wait的非阻塞版本

即,在调用了waitpid后,使父进程不被挂起,而立刻返回,提供了WNOHANG这样的一个选项

下面是一个演示的例子

#include
#include
#include
#include
#include
#include
int main(int argc,char **argv)
{
   int status;                              /*用来记录wait返回值*/
   int pid;                                 /*记录fork的返回值*/
   int exit_code,k;                           /*设置退出码,用于获取结束信息*/

   printf("fork start!\n");
   pid = fork( );                      /*新进程已经建立,父子进程并行,或者说是操作系统对进程的快速切换*/

   switch(pid)
   {
      case 0:
         k = 4;                             /*之后子进程循环操作,使父进程阻塞的依据*/
         exit_code = 45;                    /*子进程设置退出码,之后可以在父进程中获取*/
         break;
      case -1:
         printf("Error!");
         break;
      default:
         exit_code = 0;
         break;
   }

   if(pid [^]!= 0)
   {
      int status;                          /*此即为wait函数中的statloc变量,用于获取退出码*/
      pid_t  child_pid;                   /*wait函数的返回值,用于取得结束的子进程的pid*/

      child_pid = wait(&status);           /*获取到退出码(退出码中储存着退出时的信息,是一个字段,可用宏取出其中信息)以及结束的子进程的pid*/

      printf("Child process has exited!It's pid is %d\n",child_pid);
      if(WIFEXITED(status))
      {
         printf("Child process has exited,It's exit_code is %d\n",WEXITSTATUS(status));   //WEXITSTTUS宏用来获取exit函数低8位
      }
      else
         printf("Child process exit abnormally\n");
   }

   while(k--)
   {
      sleep(2);
      printf("Child process is running!\n");
   }
   exit(exit_code);
}

结果:

可以看到父进程处于阻塞状态,即在等待子进程的结束


以上即为基本的进程操作,如果代码有问题,可以到我的Github上来进行查看

传送门

下次会谈谈进程控制中的其他操作,诸如改变进程的优先级,用户的权限操作之类的,

如果本篇Blog有问题,可以在评论区中提出

July 27, 2017 11:00 AM

你可能感兴趣的:(Linux_C,操作系统,进程)