【Linux系统:进程控制】(二)

简介: 【Linux系统:进程控制】(二)

3.3 获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)

06cb7d02eea74758b62c762150aedf0a.png

这个我们之前已经讲过了,获取终止信号用的是status&0x7f,获取退出状态可以用(status>>8)&0xff,也可以用上面我们讲到的宏(WEXITSTATUS)来处理。

但是在前面进程非阻塞代码那里我们只是用了一个打印语句来执行父进程在做其他的事情,如何写的更加真实漂亮一些呢?我们这里可以给出一些补充仅供参考:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 //预设置一些任务
  7 #define TASK_NUM 10
  8 void sync_disk()
  9 {
 10   printf("这是一个数据刷新的任务\n");
 11 }
 12 
 13 void sync_log()
 14 {
 15   printf("这是一个同步日志的任务\n");
 16 }
 17 
 18 void net_sent()
 19 {
 20   printf("这是一个网络发送的任务\n");
 21 }
 22 
 23 typedef void (*fun)();                                                                                                  
 24 
 25 fun other_task[TASK_NUM]={NULL};
 26 
 27 int load_disk(fun f)
 28 {
 29   int i=0;
 30   for(;i<TASK_NUM;i++)
 31   {
 32     if(other_task[i]==NULL) break;
 33   }
 34   if(i==TASK_NUM) return -1;
 35   other_task[i]=f;
 36   return 0;                                                                                                             
 37 }
 38 
 39 void init_task()
 40 {
 41 
 42   int i=0;
 43   for(;i<TASK_NUM;i++) other_task[i]=NULL;
 44   load_disk(sync_log);
 45   load_disk(sync_disk);
 46   load_disk(net_sent);
 47 }
 48 
 49 void run_task()
 50 {
 51   int i=0;
 52   for(;i<TASK_NUM;i++)
 53   {
 54     if(other_task[i]==NULL) continue;                                                                                   
 55     else other_task[i]();
 56   }
 57 }
 58 int main()
 59 {
 60   pid_t id=fork();
 61   if(id==0)
 62   {
 63     //child
 64     int cnt=5;
 65     while(cnt)
 66     {
 67       printf("我是一个子进程,我还活着呢,cnt:%d,我的pid:%d ,ppid:%d \n",cnt--,getpid(),getppid());
 68       sleep(1);
 69     }
 70     exit(0);
 71   }
 72   //parent
 73   init_task();
 74   while(1)
 75   {
 76     int status=0;
 77     int ret_pid=waitpid(id,&status,WNOHANG);
 78     //bootprintf("我是一个父进程,我的pid:%d\n",getpid());
 79     if(ret_pid<0)
 80     {
 81       printf("wait error\n");                                                                                           
 82       exit(1);
 83     }
 84     else if(ret_pid>0)
 85     {
 86       //printf("wait success\n");
 87       if(WIFEXITED(status))
 88       {
 89         printf("wait success 退出码为:%d\n",WEXITSTATUS(status));
 90      }
 91       else
 92       {
 93         printf("wait success 退出信号为:%d\n",status&0x7f);
 94       }
 95       break;
 96     }
 97     else 
 98     {
 99       //printf("我是一个父进程,我再做其他的事情\n");
100       run_task();
101       sleep(1);
102       continue;
103     }
104   } 
105   return 0;
106 }

运行结果:

983f04b86d954ddfaf170a5cbdc820d9.png

4 进程程序替换

4.1 替换原理

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

9c488b0509ec469caad1cf4f27b7c93e.png

我们之前说过当fork之后父子进程的代码是共享的,数据会通过写时拷贝来进行处理,但是现在我们可以通过调用exec函数来帮助我们替换代码,话不多说,我们先来简单的认识一下:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6   printf("begin:       !!!\n");
  7   printf("begin:       !!!\n");
  8   printf("begin:       !!!\n");
  9   printf("begin:       !!!\n");
 10   printf("我是一个进程:pid():%d\n",getpid());
 11   execl("/bin/ls","ls","-l","-a",NULL);                                                                                 
 12   printf("end:         !!!\n");
 13   printf("end:         !!!\n");
 14   printf("end:         !!!\n");
 15   printf("end:         !!!\n");
 16   return 0;
 17 }

我们运行一下:


fd86b5918d8f4add92bc5a4cfd3b2a12.png

不难发现程序中有begin,但是很奇怪没有end,这就是因为程序代码在execl之后就被替换了,所以后面的代码根本就不会执行。

4.2 替换函数

像与execl这种函数类似的还有另外的几个,我们一个一个来看:

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

首先来介绍第一个函数execl,第一个参数是路径,就是我们要执行的进程的绝对路径,后面的参数我们之前在命令行上怎样敲的命令就用""将我们的命令分割出来,最后在用NULL结束;

第4个函数execv,第一个参数与execl一样,写出该进程的绝对路径,第二个参数是一个指针数组类型,我们将在命令行上写的命令放在该数组中就行。

我们再来看看第二个函数execlp,第一个参数是执行进程的名称,ps(这里不需要写绝对路径,由于系统会自动在环境变量PATH中查找),第二个参数与execl第二个参数一样。

再来看看第三个函数execle,在讲这个函数之前我们试试能否用一个程序调用另外一个程序:

我们创建一个c++文件:

  1 #include<iostream>                                                                                                      
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 using namespace std;
  5 
  6 int main()
  7 {
  8   cout<<"我是另一个程序,我的pid是:"<<getpid()<<endl;
  9   cout<<"MYENV: "<<(getenv("MYENV")==NULL?"NULL":getenv("MYENV"))<<endl;
 10 
 11   return 0;
 12 }

当我们运行时:

ff35d37bbb97437db08d48e4d15ade8c.png

可是当我们在test.c中调用该程序时:

69fec742915348a6a479165f710c519c.png

可见环境变量已经继承给子进程了。

test.c:

  #include<stdio.h>
  #include<stdlib.h>
  #include<unistd.h>
  #include<sys/types.h>
  #include<sys/wait.h>
  int main()
  {
    extern char** environ;
    pid_t id=fork();
    if(id==0)
    {
      //child
      printf("我是子进程,我的pid为:%d\n",getpid());
      char* const myenv[]={
        "MYENV=YouCanSeeMe",
        NULL
      };
      execle("../enc/other","other",NULL,myenv);
      exit(1);
    }
    //parent
    printf("我是父进程,我的pid是:%d\n",getpid());                                                                                                            
    int status=0;
    pid_t pid=waitpid(id,&status,0);
    if(pid<0)
    {
      printf("wait error\n");
    }
    else
    {
      if(WIFEXITED(status))
      {
        printf("wait success,exit code:%d\n",WEXITSTATUS(status));
      }
      else{
        printf("wait success,singal:%d\n",status&0x7f);
      }
   }
    return 0;
  }

但是当我们修改other.cc代码:

    1 #include<iostream>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 using namespace std;
    5 
    6 int main()
    7 {
    8   cout<<"我是另一个程序,我的pid是:"<<getpid()<<endl;
    9   cout<<"MYENV: "<<(getenv("MYENV")==NULL?"NULL":getenv("MYENV"))<<endl;
E> 10   cout<<"PATH: "<<(getenv("PATH")==NULL?"NULL":getenv("PATH"))<<endl;                                                  
   11   return 0;
   12 }

d507801388da4b3b97822ada346efd1f.png

我们发现当我们用test.c调用other.cc程序时添加的环境变量居然把PATH给覆盖了。有什么方法可以解决吗?

答案是有的,我们在test.c中可以通过putenv函数将环境变量导入到environ中,我们使用environ将环境变量传给被调用的程序。

test.c:

  #include<stdio.h>
  #include<stdlib.h>
  #include<unistd.h>
  #include<sys/types.h>
  #include<sys/wait.h>
  int main()
  {
    extern char** environ;
    pid_t id=fork();
    if(id==0)
    {
      //child
      printf("我是子进程,我的pid为:%d\n",getpid());
     /* char* const myenv[]={
        "MYENV=YouCanSeeMe",
        NULL
      };*/
      putenv("MYENV=YouCanSeeMe");
      execle("../enc/other","other",NULL,environ);
      exit(1);
    }
    //parent                                                                                                                                                   
    printf("我是父进程,我的pid是:%d\n",getpid());
    int status=0;
    pid_t pid=waitpid(id,&status,0);
    if(pid<0)
    {
      printf("wait error\n");
    }
    else
    {
      if(WIFEXITED(status))
      {
        printf("wait success,exit code:%d\n",WEXITSTATUS(status));
      }
      else{
        printf("wait success,singal:%d\n",status&0x7f);
      }
    }
    return 0;
  }

当我们运行时:

621f7dc3e32140ed95782259521c8542.png

我们通过上面的栗子来思考下为什么环境变量具有全局属性(子进程为什么会继承父进程的环境变量),也就是我们究竟是通过什么方式办到的?

相信大家此时已经有了答案,我们是通过exec函数将环境变量通过参数传递过去的。

那我们再思考一下,我们在test.c中不通过putenv来获得设置变量,而是在命令行上通过export来导入环境变量,这样可行吗?

我们分析分析,当我们export导入环境变量是导入在bash中的,而bash是我们创建的test.c的父进程,而子进程会继承父进程的环境变量,所以bash就将它的环境变量传递给了它的孙子进程,故是可以的,我们来验证验证:

40ba6698ad7b4512bc813370eb30d94b.png

结果与我们预想的一样。(ps:此时在test.c中我们是屏蔽了putenv的)

4.3 函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

4.4 命名理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
函数名 参数格式 是否带路径 是否使用当前环境变量
execl 列表
execlp 列表
execv 数组
execvp 数组
execle 列表 否,需要自己组装环境变量
execve 数组 否,需要自己组装环境变量

表格中需要大家注意的是:是否带路径表示的是能不能去环境变量PATH中找,若带了路径,说明在环境变量中能够找到就不需要写出绝对路径,只写要执行文件的名字就好了,若没有带路径则说明需要自己补全路径来找寻该文件的位置。

事实上 , 只有 execve 是真正的系统调用 , 其它五个函数最终都调用 execve, 所以 execve 在 man 手册 第 2 节 , 其它函数在man 手册第 3 节,其他的函数都是execve的封装。


e3318be90cb042c087160ea7d8b1d7a5.png

目录
相关文章
|
7月前
|
Ubuntu Linux Anolis
Linux系统禁用swap
本文介绍了在新版本Linux系统(如Ubuntu 20.04+、CentOS Stream、openEuler等)中禁用swap的两种方法。传统通过注释/etc/fstab中swap行的方式已失效,现需使用systemd管理swap.target服务或在/etc/fstab中添加noauto参数实现禁用。方法1通过屏蔽swap.target适用于新版系统,方法2通过修改fstab挂载选项更通用,兼容所有系统。
612 3
Linux系统禁用swap
|
7月前
|
Linux
Linux系统修改网卡名为eth0、eth1
在Linux系统中,可通过修改GRUB配置和创建Udev规则或使用systemd链接文件,将网卡名改为`eth0`、`eth1`等传统命名方式,适用于多种发行版并支持多网卡配置。
1142 3
|
6月前
|
监控 关系型数据库 MySQL
在CentOS系统中,如何统计哪个进程打开了文件描述符?
利用上述方法,你可以有效地监控和统计CentOS系统中的进程打开的文件描述符数量,以帮助排查错误或优化系统配置。通过组合使用各种工具和命令,可以获得对系统状态和行为的深入了解,进而做出相应的调整和
288 5
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
713 3
Linux系统初始化脚本
|
8月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
542 18
|
7月前
|
安全 Linux Shell
Linux系统提权方式全面总结:从基础到高级攻防技术
本文全面总结Linux系统提权技术,涵盖权限体系、配置错误、漏洞利用、密码攻击等方法,帮助安全研究人员掌握攻防技术,提升系统防护能力。
813 1
|
7月前
|
监控 安全 Linux
Linux系统提权之计划任务(Cron Jobs)提权
在Linux系统中,计划任务(Cron Jobs)常用于定时执行脚本或命令。若配置不当,攻击者可利用其提权至root权限。常见漏洞包括可写的Cron脚本、目录、通配符注入及PATH变量劫持。攻击者通过修改脚本、创建恶意任务或注入命令实现提权。系统管理员应遵循最小权限原则、使用绝对路径、避免通配符、设置安全PATH并定期审计,以防范此类攻击。
1236 1
|
8月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
2137 10
|
8月前
|
安全 Linux 数据安全/隐私保护
为Linux系统的普通账户授予sudo访问权限的过程
完成上述步骤后,你提升的用户就能够使用 `sudo`命令来执行管理员级别的操作,而无需切换到root用户。这是一种更加安全和便捷的权限管理方式,因为它能够留下完整的权限使用记录,并以最小权限的方式工作。需要注意的是,随意授予sudo权限可能会使系统暴露在风险之中,尤其是在用户不了解其所执行命令可能带来的后果的情况下。所以在配置sudo权限时,必须谨慎行事。
1391 0
|
8月前
|
Ubuntu Linux 开发者
国产 Linux 发行版再添新成员,CutefishOS 系统简单体验
当然,系统生态构建过程并不简单,不过为了帮助国产操作系统优化生态圈,部分企业也开始用国产操作系统替代 Windows,我们相信肯定会有越来越多的精品软件登录 Linux 平台。
617 0

热门文章

最新文章