Linux系统2--进程控制

1.fork()函数

函数原型 :pid_t fork(void);
函数作用 :用于创建一个进程,所创建的子进程复制父进程的代码段/数据段/BSS段/堆/栈等所有用户空间信息;在内核中操作系统重新为其申请了一个PCB,并使用父进程的PCB进行初始化;
如图:
在这里插入图片描述
注意:当执行/a.out中有fork子进程时,
shell进程将不知道a.out创建了子进程,因此在shell检测到父进程执行完毕后,shell会直接切换到前台。因此当父进程比子进程先抢到cpu进行处理时,shell将不会管子进程而直接返回到前台。
避免这种情况的发生,可以让父进程sleep一会即可。
函数返回值: >0,为父进程返回值。=0,子进程返回值。<0,fork失败。显然我们可以根据fork函数的返回值来判断当前进程是父进程还是子进程。
子进程创建成功后,代码的执行位置 :父进程执行到哪里,子进程就从哪里开始执行(fork以后)
如图:
在这里插入图片描述

父子进程的执行顺序 :父子进程的执行顺序是什么呢?答案是不确定的。因为进程之间是相互竞争去抢CPU的。谁抢到是不一定的。
怎么得到进程的ID:使用getpid()得到当前进程的id,使用getppid得到当前进程的父进程的 id
fork函数的使用1—创建一个子进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
  pid_t pid;
  int n;
  pid = fork();
  if(pid < 0)
  {
    perror("fork");
    exit(1);
  }
  if(pid == 0)
  {
    printf("this is child\n");
  }
  if(pid > 0)
  {
    printf("this is parent\n");
    sleep(1);
  }
  printf("=======1======\n");
  printf("=======2======\n");
  printf("=======3======\n");
  printf("=======4======\n");
  return 0;
}

fork函数使用–循环创建多个子进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
  int i = 0;
  int count = 200;//进行全局变量是否能够在父子进程之间进行传递的测试
  int number = 3;//生成的子进程的数量
  pid_t pid;//fork返回值的定义
  for ( ; i<number; i++)
  {
    pid = fork();
    if(pid == 0)break;
  }
  if(i == 0)
  {
    printf("this is first child  id=%d pid=%d",getpid(),getppid());
    count+=200;
    printf("count = %d\n",count);
  }
  if(i == 1)
  {
    printf("this is second child id=%d pid=%d",getpid(),getppid());
    count+=200;
    printf("count = %d\n",count);
  }
  if(i == 2)
  {
    printf("this is third child id=%d pid=%d",getpid(),getppid());
    count+=200;
    printf("count = %d\n",count);
  }
  if(i == 3)
  {
    printf("this is parent id=%d",getpid());
    count+=400;
    printf("count = %d\n",count);
    sleep(1);
  }
  return 0;
}

上面的代码所战士的结果为:
在这里插入图片描述
显然,这验证了全局变量不能在父子进程之间进行传递。即进程之间不共享全局变量。Why???
如图:
在这里插入图片描述如图,全局变量num=100,进程之间对全局变量是读时共享,写时复制的原则。在读num时,都是读取到num=100,但是在对内存中的num进行写时,会将num=100复制一份,然后分别对num进行对应进程的操作后,分别写到对应的num中。在父进程num=99写到父进程num中,子进程num=101写到子进程num中。

2.exec函数族

exec函数族不是一个函数。在linux系统中,exec函数族包括:execl,execv,execle,execve,execlp,execvp函数。针对这几个函数的用法及介绍将在下面介绍。
exec函数族目的是为了在fork()后,让进程执行另外一些我们想要执行的程序。exec函数族能够根据指定的文件名或目录名找到可执行文件,并替换进程地址空间中的数据段、代码段、堆栈段。
如下图:
在这里插入图片描述
使用exec函数族的场景:
1.当一个进程想要执行另一个进程时,此时利用fork建立子进程,再在子进程中调用exec函数族中的函数来使子进程执行我们想要执行的进程。
2.当进程认为自己不能为系统和用户做出贡献时,就可以调用exec函数族中的函数,让自己执行另一个想要执行的进程。
使用示例
这里先简单介绍一下execl函数,其他函数将在下面详述:
int execl(const char path, const chararg, …);一般用来执行自定义的应用程序

  • path: 要执行的程序的绝对路径
  • 变参arg: 要执行的程序的需要的参数
  • 第一arg:占位(不能空,随便写点,一般系写命令的名字)
  • 后边的arg: 命令的参数
  • 参数写完之后: NULL
  • 一般执行自己写的程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
  pid_t pid = fork();
  if(pid == 0)
  {
    execl("hellotest","hellotest",NULL);
    perror("execl");
    exit(1);
  }
  if(pid > 0)
  {
    printf("parent pid=%d\n ",getpid());
  }
}

以上代码是为了让子进程执行当前文件夹下的hellotest程序。结果为:
在这里插入图片描述
exec函数族—6个exec函数
<1>执行指定目录下的程序:execl,execv函数:

  • int execl(const char *path, const char *arg,…);
    path: 要执行的程序的绝对路径
    变参arg: 要执行的程序的需要的参数
    第一arg:占位(不能空,随便写点,一般系写命令的名字)
    后边的arg: 命令的参数
    参数写完之后: NULL
    一般执行自己写的程序
  • int execv(const char *path, char const argv[]);
    参数:
    path:指定程序的路径,argv:用户传递的参数
    示例:
    path = /bin/ps
    char
    args[] = {“ps”,“aux”, NULL};
    execv("/bin/ps", args);

<2> 执行PATH环境变量能够搜索到的程序

  • int execlp(const char file, const chararg, …);
    参数:
    file: 执行的命令的名字(直接写命令即可)
    第一arg:占位
    后边的arg: 命令的参数
    参数写完之后: NULL
    该函数是执行系统自带的程序:
    1.在/bin
    2. execlp执行自定义的程序: file参数为绝对路径
  • int execvp(const char *file, char *const argv[]);
    类似于execv与execl的关系

<3> 执行指定路径, 指定环境变量下的程序

  • int execle(const char *path, const char *arg, …, char const envp[]);
    path: 执行的程序的绝对路径
    如:/home/itcast/a.out
    arg: 执行的的程序的参数
    envp: 用户自己指定的搜索目录, 替代PATH
    如:char
    envp[] ={"/home/itcast", “/bin”, NULL};
  • int execve(const char *path, char *const argv[], char *const envp[]);

补充:对于PATH环境变量的补充:
原文出处:http://c.biancheng.net/view/5970.html

3.进程回收

由于代码编写的不完善,随着时间的推移,应用程序的性能会越来越低,有时会陷于某一循环中,导致不必要的 CPU 负载。这些应用程序还可能导致内存泄漏,这时应用程序不再将不需要的内存释放回操作系统。这些应用程序可能会导致服务器停止运行,因此需要重新启动服务器。进程回收就是为解决这些问题而创建的。

导致这些问题出现的一些问题进程:

孤儿进程

  • 父生子
  • 父先死,子还活着,则子进程称为孤儿进程
  • 孤儿进程被系统的init进程(Ubuntu16 以后就变成了UI进程)领养,则init进程就变成了孤儿进程的父进程,这样的目的是为了释放子进程占用的系统资源。(进程结束之后,能够释放用户区空间,但是释放不了PCB,必须由父进程释放)
  • 示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
  pid_t pid = fork();
  if(pid == 0)
  {
    printf("child pid=%d ppid=%d \n",getpid(),getppid());
    //因为父进程死亡,则getppid得到的为UI进程的pid
    sleep(1);//给子进程sleep,不给父进程sleep那么就可以实现父进程死了,但子进程还存在的情况。
  }
  else 
    printf("parent pid=%d\n",getpid());
  return 0;
}

僵尸进程

  • 孩子死了,父亲还活着,但是父进程不去释放子进程的PCB,这样的子进程称为僵尸进程。
  • 僵尸进程是一个死进程,不是一个正常活着的进程。
    示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
  pid_t pid = fork();
  if(pid == 0)
  {
    printf("child pid=%d ppid=%d \n",getpid(),getppid());
  }
  else 
  {
    while(1)
    {
    printf("parent pid=%d\n",getpid());
    sleep(1);
    }//让父亲一直不死,释放不了孩子的空间
  }
  return 0;
}

在ps anux打印出的信息中就有一个Z+即僵尸进程的首字母,如图:
在这里插入图片描述

怎么结束僵尸进程?
杀死父进程-------那么僵尸进程就变为孤儿进程,就可以被领养,接着释放。
kill -9 4898

如何进行进程回收工作

linux提供了两个函数来实现进程回收工作:pid_t wait(int *status);pid_t waitpid(pid_t pid,int *statuc,int options);

wait函数

  • wait函数是一个阻塞函数,阻塞条件为:子进程是否死亡
  • 调用一次回收一个子进程资源。
  • 返回值:
    负1:回收失败,则说明已经没有子进程了
    大于0:回收子进程对应的pid
  • 参数:status–传出参数
    判断子进程是如何死的?
    (1)获取退出时候的返回值(正常退出)
    如:return 0;exit(0);
    //若正常退出,则WIFEXITED(status) > 0
    //那么可以使用WEXITSTATUS(status)来获得结束状态
    (2)被哪个信号杀死,如:kill中的信号,9为杀死进程的信号
    //若被某个信号杀死则WIFSIGNALED(status) > 0
    //使用 WTERMSING(status)来得到是什么信号杀死了进程
    示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
  pid_t pid = fork();
  if(pid < 0)
  {
    perror("fork");
    exit(1);
  }
  if(pid > 0)
  {
    printf("this is parent\n");
    int status;
    wait(&status);
    if(WIFEXITED(status))
    {
    printf("exit status is %d\n",WEXITSTATUS(status));
    }//正常杀死
    if(WIFSIGNALED(status))
    {
    printf("exit single id %d\n",WTERMSIG(status));
    }//通过信号杀死
  }
  if(pid == 0)
  {printf("this is child\n");sleep(200);}//为了测试被信号杀死,因此让子进程先睡200秒,然后用kill -9杀死子进程
  return 2;
}

waitpid函数
id_t waitpid(pid_t pid, int *status, int options);使用该函数不仅仅只回收一个子进程

  • 返回值:
    负1:回收失败,没有子进程
    大于0:被回收子进程的pid
    如果为非阻塞:=0:子进程正处于运行态。
  • 参数
    (1)pid:
    大于0:某个子进程的pid
    等于-1:回收所有子进程,如,可以用来循环回收所有子进程:
while( (wpid =waitpid(-1, &status, WNOHANG) != -1))

等于0:回收当前进程组的所有子进程,意思就是当父进程的子进程送给别的父进程,此时回收的话只回收当前父进程组内的所有子进程,但不包括送出去的子进程。
小于0:该参数为子进程的pid取反(加减号),也就是当父进程想要回收送出去的子进程时,只需要在送出去的子进程pid前加减号即可。
(2)options:
等于0:waitpid为阻塞状态
WNOHANG:waitpid为非阻塞状态

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
  int i = 0;
  pid_t pid;
  for(;i < 3;++i)
  {
    pid = fork();
    if(pid == 0)break;
  }
  if(i == 0)
  {
    execl("/home/sun123/sunjiasen/day5/error","error",NULL);
    perror("execl1");
    exit(1);
  }
  else if(i == 1)
  {
    execlp("ls","ls","-l",NULL);
    perror("execlp");
    exit(1);
  }
  else if(i == 2)
  {
    execl("/home/sun123/sunjiasen/day5/test","test",NULL);
    perror("execl2");
    exit(1);
  }
  else if(i == 3)
  {   
    printf("tnis is parent pid=%d\n",getpid());
    int status;
    pid_t wpid;
    while((wpid =waitpid(-1, &status, WNOHANG) != -1))
    {
    printf("%dth child reversed",wpid);
    if(WIFEXITED(status))
    {
      printf("return value is %d\n",WEXITSTATUS(status));
    }
    if(WIFSIGNALED(status))
    {
      printf("killed by%d single\n",WTERMSIG(status));
    }
    }
  }
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值