进程的控制

一.进程创建

1.fork函数

(1)为什么要给子进程返回0,给父进程返回子进程的pid?
父子进程立场:父进程不需要标识,子进程需要标识;子进程是要执行程序的,父进程需要区分子进程,子进程不需要(就是父亲要辨别是哪一个孩子,而父亲只有一个,不用分辨)

(2)如何理解fork有两个返回值的问题?
fork只是代码的集合,在fork内部就已经实现分流,在return之前执行两次,则返回两个值
在这里插入图片描述

2.写时拷贝

通常,父子进程代码共享(共享:即父子进程对应的页表指向的是同一块物理内存),父子不再写入时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式各自一份副本。
写时拷贝的过程:
在这里插入图片描述
为何要写时拷贝:
①进程具有独立性
②不在创建时分开:子进程不一定会使用父进程的所有数据,写入的本质是:需要的时候,按需分配
延时分配:需要的时候再给你,本质是可以高效可以使用任何内存空间

代码会不会写时拷贝:90%不会,只读的不会

3.fork常规用法

①父进程复制自己,使父子进程同时执行不同的代码
②一个进程要执行一个不同的程序

4.fork调用失败原因:

①系统中有太多的进程
②实际用户的进程数超过了限制

二、进程终止

1.退出场景

①代码运行完毕,结果正确
②代码运行完毕,结果不正确
③代码异常终止

2.进程退出码

(1)echo $? :显示打印最近一次进程的退出码(默认正确退出,退出码为0)
(2)进程退出码每个数字代表的含义不一样,能帮助用户确认失败的原因,类似与计网网页出现的错误码,不同错误码代表出现的错误不一样
程序代码:

#include<stdio.h>
#include<string.h>

int main()
{
  for(int i = 0; i < 100; i++)
  {
    printf("%d: %s\n",i,strerror(i));
  }
  return 0;
}

在这里插入图片描述
(3)main返回值给了谁?—— OS
为什么main函数要有返回值?
运行程序,加载,形成进程

3.进程退出方法

① 从main返回,只有main函数中的return代表进程退出
② 调用exit,在代码中的任何地方,调用exit都代表进程退出
③_exit函数,也代表进程的终止
②与③的差别:exit会释放进程曾经占用的资源,比如:缓冲区,_exit直接终止进程,不会做任何收尾工作!

注意:进程异常退出,退出码没有了意义

进程终止后,OS做了什么?
释放曾经申请的数据结构,释放曾经申请的内存,从各种队列等数据结构中移除

三、进程等待

1.进程等待的原因

进程等待:通常由父进程完成
原因:(1)回收子进程资源 (2)获取子进程退出信息

2.进程等待的方法

(1)wait方法
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
  pid_t id = fork();
  if(id  == 0)
  {
    //child
    int count = 0;
    while(count < 10)
    {
      printf("I am child:pid:%d,ppid:%d\n",getpid(),getppid());
      count++;
      sleep(1);
    }
    exit(0);
  }    
    else{
      //father
      
      printf("I am father:pid:%d,ppid:%d\n",getpid(),getppid());
      pid_t ret = wait(NULL);
      if(ret > 0)
      {
        printf("wait child success!,%d\n",ret);
      }
      printf("father run......\n");
      sleep(10);
    }
  return 0;
}

在这里插入图片描述

结论:
①在子进程运行期间,父进程wait的时候,父进程在阻塞等待子进程退出
②父子谁先运行不确定,但是wait之后,大部分情况都是子进程先退出,父进程读取子进程退出信息,父进程才退出

(2)waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
a.当正常返回的时候waitpid返回收集到的子进程的进程ID;
b.如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
c.如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
父进程等待成功,并不意味着子进程运行成功,只是子进程退出,要判定子进程是否运行成功要看status判定
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出),本质是检查信号
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
利用宏定义获取status,而不是移位:

if( WIFEXITED(status) && ret == pid ){ 
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
} 
else{
printf("wait child failed, return.\n");
return 1;
}

options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
  pid_t id = fork();
  if(id  == 0)
  {
    //child
    int count = 0;
    while(count < 10)
    {
      printf("I am child:pid:%d,ppid:%d\n",getpid(),getppid());
      count++;
      sleep(1);
    }
    exit(0);
  }    
    else{
      //father
      
      printf("I am father:pid:%d,ppid:%d\n",getpid(),getppid());
    //  pid_t ret = wait(NULL);
     int status = 0;
      pid_t ret = waitpid(id,&status,0);
      if(ret > 0)
      {
        printf("wait child success!,%d\n",ret);
      }
      printf("father run......\n");
      sleep(10);
    }
  return 0;
}

在这里插入图片描述

进程等待成功,是否意味着子进程运行成功?
不是,进程等待成功只意味着子进程退出了,是否运行成功要看status状态

(3)获取子进程status
需要右移8位并与0xFF,得到status状态信息
(status >> 8)& 0xFF
在这里插入图片描述
在这里插入图片描述
把exit函数的退出码改变,获取退出码,说明子进程运行成功
在这里插入图片描述
在这里插入图片描述
注意:进程异常的时候,本质是进程运行的时候出现了某种错误,导致进程收到信号!
查看进程收到的信号:
第一种:运行程序,发送kill -2 进程号(2为终止进程信号),程序退出获得child get signal:2
第二种:程序本身有错

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
  pid_t id = fork();
  if(id  == 0)
  {
    //child
    int count = 0;
    while(count < 10)
    {
      printf("I am child:pid:%d,ppid:%d\n",getpid(),getppid());
      count++;
      if(count == 5)
      {
      	int a = 1/0;
      }
      sleep(1);
    }
    exit(0);
  }    
    else{
      //father
      
      printf("I am father:pid:%d,ppid:%d\n",getpid(),getppid());
    //  pid_t ret = wait(NULL);
     int status = 0;
      pid_t ret = waitpid(id,&status,0);
      if(ret >= 0)
      {
        printf("wait child success!,%d\n",ret);
        printf("status:%d\n",status);
        printf("child xeit code:%d\n",(status>>8)&0xFF);
        printf("child get signal:%d\n",status&0x7F);
      }
      printf("father run......\n");
      sleep(10);
    }
  return 0;
}

在这里插入图片描述
8为SIGFPE信号,浮点数报错
查看不同信号值所代表的意义:1-31:普通信号;34-64:实时信号
在这里插入图片描述
(4)基于非阻塞接口的轮询
即:父进程在等待子进程退出的过程中,不会傻傻地等待,还会干自己的事!

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
  pid_t id = fork();
  if(id  == 0)
  {
    //child
    int count = 0;
    while(count < 10)
    {
      printf("I am child:pid:%d,ppid:%d\n",getpid(),getppid());
  //    count++;
      sleep(1);
      count++;
    }
    exit(1);
  }    
    while(1){
      
      int status = 0;
      pid_t ret = waitpid(id,&status,WNOHANG);  //WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
      if(ret > 0)
      {
        printf("wait child success!\n");
        printf("exit code:%d\n",WEXITSTATUS(status));
        break;  //若等待成功,需要退出
     //   printf("status:%d\n",status);
       // printf("child exit code:%d\n",(status>>8)&0xFF);
      //  printf("child get signal:%d\n",(status) & 0x7F);
      }
      else if(ret == 0)
      {
        //child not quit,waitpid success
        printf("father do other thing!\n");
        sleep(1);
      }
      else{
        printf("waitpid error!\n");
        break; //等待失败,也要跳出循环
      }
     // printf("father run......\n");
     // sleep(10);
    }
  return 0;
}

在这里插入图片描述

四、进程程序替换

1.替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

结论:
(1)当前进程在进行程序替换的时候,没有创建新的进程
(2)进程程序替换,一经替换,后续代码不会执行,绝不返回!
(3)程序替换,可能失败(如:文件不存在),程序后续代码运行不会受到影响
execl系列函数,不需要判断返回值,只要返回就是失败
(4)进程替换,不会影响进程打开的文件信息(只会影响其映射关系,不会影响其数据结构),子进程会继承父进程打开的文件信息

2.替换函数

l(list) : 表示参数采集列表,要带路径
v(vector) : 参数用数组,要带路径,用一个数组先表示出来
p(path) : 有p自动搜索环境变量PATH,不用带路径
e(env) : 表示自己维护环境变量

(1)execl函数

#include<stdio.h>
#include<unistd.h>

int main()
{
  printf("I am a process!\n");
  sleep(5);
  execl("/usr/bin/ls","ls","-a","-i","-l",NULL);  //NULL结尾表示结束
  return 0;
}

在这里插入图片描述
父进程让子进程(子进程去查找路径发现错误,不会执行execl函数中的内容)去执行其它的:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
     printf("I am a process!\n");
     sleep(5);
     execl("/usr/bio/ls","ls","-a","-i","-l",NULL);  //NULL结尾表示结束
     exit(10);
  }
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  if(ret > 0)
  {
    printf("signal:%d\n",status & 0x7F);
    printf("exit code:%d\n",(status>>8) & 0xFF);
  }
  return 0;
}

在这里插入图片描述
路径改回来后:
在这里插入图片描述
(2)execlp函数

//与execl函数不同的是,不用写路径,p会自动去找路径,要执行的是谁+怎么执行即可
execlp("ls","ls","-a","-i","-l",NULL); 

在这里插入图片描述
(3)execv函数

char *myargv[] = {"ls","-a","-i","-l",NULL};
execv("usr/bin/ls",myargv);

在这里插入图片描述
(4)execvp函数

char *myargv[] = {"ls","-a","-i","-l",NULL};
execvp("ls",myargv);

在这里插入图片描述

3.makefile一次生成多个可执行文件

(1)exec.c文件里面去执行cmd.c文件里的内容(也可以调用脚本文件、python文件、c++文件等)

a.makefile文件:

.PHONY:all
all:myexec mycmd

myexec:exec.c
	gcc -std=c99 -o $@ $^

mycmd:cmd.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f myexec mycmd

b.cmd.c文件

#include<stdio.h>

int main()
{
  printf("I am a new exe,my cmd!\n");
  return 0;
}

c.exec.c文件

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
     printf("I am a process!\n");
     sleep(5);
    // execl("/usr/bin/ls","ls","-a","-i","-l",NULL);  //NULL结尾表示结束
   // execlp("ls","ls","-a","-i","-l",NULL);
     char *myargv[] = {"ls","-a","-i","-l",NULL};
     //execv("/usr/bin/ls",myargv);
    // execvp("ls",myargv);
    execl("./mycmd","mycmd");
     exit(10);
  }
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  if(ret > 0)
  {
    printf("signal:%d\n",status & 0x7F);
    printf("exit code:%d\n",(status>>8) & 0xFF);
  }
  return 0;
}

在这里插入图片描述
在这里插入图片描述(2)调用环境变量
调用系统的环境变量:

#include<stdio.h>
#include<stdlib.h>

int main()
{
  printf("I am a new exe,my cmd!\n");
  printf("my env:%s\n",getenv("MYENV"));
  printf("OS env:%s\n",getenv("PATH"));
  return 0;
}

execl("./mycmd","mycmd",NULL);

在这里插入图片描述
调用自定义的环境变量:

char *myenv[]= {"MYENV=helloworld",NULL};
execle("./mycmd","mycmd",NULL,myenv);

在这里插入图片描述
可看到自己修改的环境变量改变,系统发环境变量为null,这种环境变量修改是覆盖式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值