linux系统编程(七)进程(下)

简介: linux系统编程(七)进程

1.3.2 getpid函数


获取当前进程ID

pid_t getpid(void);


1.3.3 getppid函数


获取当前进程的父进程ID

pid_t getppid(void);

区分一个函数是“系统函数”还是“库函数”依据:

② 是否访问内核数据结构

② 是否访问外部硬件资源  二者有任一 → 系统函数;二者均无 → 库函数


1.3.4 getuid函数


获取当前进程实际用户ID
uid_t getuid(void);
  获取当前进程有效用户ID
uid_t geteuid(void);


1.3.5 getgid函数


获取当前进程使用用户组ID
gid_t getgid(void);
  获取当前进程有效用户组ID
gid_t getegid(void);


1.3.6 进程共享

父子进程之间在fork后。有哪些相同,那些相异之处呢?
刚fork之后:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程ID   2.fork返回值   3.父进程ID    4.进程运行时间    5.闹钟(定时器)   6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。   
练习:编写程序测试,父子进程是否共享全局变。              【fork_shared.c】
重点注意!躲避父子进程共享全局变量的知识误区!
【重点】:父子进程共享:1. 文件描述符(打开文件的结构体)  2. mmap建立的映射区 (进程间通信详解)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int var =100;
int main()
{
        pid_t pid;
        pid = fork();
        if(pid == -1){
                perror("fork error");
                exit(1);
        }else if(pid>0)
        {
                var = 288;
                printf("parent ,var = %d\n",var);
                printf("i'am parent pid = %d,getppid =%d\n",getpid(),getppid());
        }
        else if(pid==0)
        {
                var = 200;
                printf("i'am child pid =%d,ppid =%d\n",getpid(),getppid());
                printf("child,var = %d\n",var);
        }
        printf("-------finish----------\n");
        return 0;
}


特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。


1.3.7 gdb调试

使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。
默认跟踪父进程。
set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。          【follow_fork.c】

1.4 exec函数族


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

将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。


其实有六种以exec开头的函数,统称exec函数:

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[]);
int execve(const char *path, char *const argv[], char *const envp[]);(只有它是系统调用)


1.4.1 execlp函数


加载一个进程,借助PATH环境变量      
int execlp(const char *file, const char *arg, ...);  成功:无返回;失败:-1
    参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
    该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc,char *argv[]){
        pid_t pid =fork();
        if(pid==-1){
        perror("fork error");
        exit(1);
        }
        else if(pid ==0 )
        {
                //execlp("ls","ls","-l","-d","-h",NULL);
                execlp("date","date",NULL);
                perror("exec error");
                exit(1);
        }else if(pid >0)
        {
                sleep(1);
                printf("I'am parent :%d\n",getpid());
        }
}


1.4.2 execl函数


加载一个进程, 通过 路径+程序名 来加载。 
    int execl(const char *path, const char *arg, ...);  成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL);      使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc,char *argv[]){
        pid_t pid =fork();
        if(pid==-1){
        perror("fork error");
        exit(1);
        }
        else if(pid ==0 )
        {
                //execlp("ls","ls","-l","-d","-h",NULL);
               // execlp("date","date",NULL);
               // execl("./a.out","./a.out",NULL);
                //execl("./a.out","./a.out",AA,BB,CC);
                 execl("/bin/ls","ls","-l",NULL);
                perror("exec error");
                exit(1);
        }else if(pid >0)
        {
                sleep(1);
                printf("I'am parent :%d\n",getpid());
        }
}
~                                                                                                  
~                                                                                                  
~


1.4.3 execvp函数


加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式: 
①... 
② argv[]  (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...)) 
变参终止条件:① NULL结尾 ② 固参指定
execvp与execlp参数形式不同,原理一致。
练习:将当前系统中的进程信息,打印到文件中。            【exec_ps.c】
...
char *argv[]={"ls","-l","-h",NULL}
execvp("ls",argv);
...


1.4.4 exec函数族一般规律


exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。

l (list)    命令行参数列表
p (path)    搜素file时使用path变量
v (vector)    使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量


事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

1670989340707.jpg

exec函数族


1.5 回收子进程


1.5.1孤儿进程

孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
【orphan.c】
1
2
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void){
  pid_t pid;
  pid= fork();
  if(pid == 0){
  while(1){
    printf("i am child, my parent pid = %d\n",getppid());
    sleep(1);
  }
  }else if(pid > 0){
  printf("i am parent ,my pid is = %d\n",getpid());
  sleep(9);
  printf("----------parent going to die ------------\n");
  }else{
  perror("fork");
  return 1;
  }
}


1.5.2 僵尸进程


僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。思考!用什么办法可清除掉僵尸进程呢?杀死父进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
  pid_t pid;
  pid =fork();
  if(pid == 0){
  printf("----child,my parent =%d,going to sleep 10s\n",getppid());
  sleep(10);
  printf("-----------------child die ----------------\n");
  }else if(pid > 0)
  {
  while(1){
    printf("i am parent ,pid = %d,myson = %d\n",getpid(),pid);
    sleep(1);
  }
  }else{
  perror("fork");
  return 1;
  }
}


1.5.3 wait函数


一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。


父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出 
② 回收子进程残留资源 
③ 获取子进程结束状态(退出原因)。
pid_t wait(int *status);  成功:清理掉的子进程ID;失败:-1 (没有子进程)


当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2.

释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号) 可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:

1.  WIFEXITED(status) 为非0 → 进程正常结束
  WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
 2.  WIFSIGNALED(status) 为非0 → 进程异常终止
  WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3.  WIFSTOPPED(status) 为非0 → 进程处于暂停状态
  WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
  WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void){
  pid_t pid,wpid;
  int status;
  pid = fork();
  if(pid == 0){
  printf("---child ,my id =%d,going to sleep 10s\n",getpid());
  sleep(10);
  printf("----------------child die---------------\n");
  return 73;
  }else if (pid > 0){
  //wpid = wait(NULL);  
  wpid = wait(&status);
  if(wpid == -1){
    perror("wait error");
    exit(1);
  }
  if(WIFEXITED(status)){
    printf("child exit with %d\n",WEXITSTATUS(status));
  }
  if (WIFSIGNALED(status)){
    printf("child kill with signal%d\n",WTERMSIG(status));
  }
  printf("-----------parent wait finish :%d\n",wpid);
  }else{
  perror("fork");
  return 1;
  }
  return 0;
  }


1.5.4waitpid函数


作用同wait,但可指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);
    成功:返回清理掉的子进程ID;
    失败:-1(无子进程)
特殊参数和返回情况:
参数pid: 
> 0 回收指定ID的子进程  
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:
参3为WNOHANG,且子进程正在运行。


注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。                            【waitpid.c】

一次调用,回收一个子进程

#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc,char *argv[])
{
        int i;
        pid_t pid,wpid,tmpid;
        for(i=0;i<5;i++){
                pid =fork();
                if(pid==0)
                {
                break;
                }
                if(i == 2){
                        tmpid = pid;
                        printf("------------pid = %d\n",tmpid);
                }
        }
        if(5==i){
                //sleep(5);
                //wait(NULL);
                //wpid = waitpid(-1,NULL,WNOHANG);
                //wpid = waitpid(tmpid,NULL,0);
                printf("i am parent,before waitpid ,pid =%d\n",tmpid);
                wpid = waitpid(tmpid,NULL,WNOHANG);
                if(wpid == -1){
                        perror("waitpid error");
                        exit(1);
                }
                printf("i am parent ,wait a child finish : %d\n",wpid);
        }else{
                sleep(i);
                printf("i'm %dth child,pid =%d\n",i+1,getpid());
        }
        return 0;
}


想回收多个。while

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc,char *argv[]){
  int i;
  pid_t pid,wpid;
  for(i=0;i<5;i++){
  pid = fork();
  if(pid == 0){
    break;
  }
  }
  if(5==i){
  /*
  *
  * while((wpid = waitpid(-1,NULL,0))){
    printf("wait child %d \n",wpid);
  }
  * 
  */
  while((wpid = waitpid(-1,NULL,WNOHANG))!=-1){
  if(wpid>0){
    printf("wait child %d\n",wpid);
  }
  else if(wpid == 0)
  {
    sleep(1);
    continue;
  }
  }
  }else{
  sleep(i);
  printf("i'm %dth child ,pid = %d\n",i+1,getpid());
  } 
}


作业:父进程fork 3 个子进程,三个子进程一个调用ps命令, 一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid对其子进程进行回收。

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
16天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
41 1
|
4天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
112 78
|
8天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
39 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
4天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
43 13
|
11天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
19天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
24天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
5天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
17 0
|
16天前
|
存储 Oracle 安全
服务器数据恢复—LINUX系统删除/格式化的数据恢复流程
Linux操作系统是世界上流行的操作系统之一,被广泛用于服务器、个人电脑、移动设备和嵌入式系统。Linux系统下数据被误删除或者误格式化的问题非常普遍。下面北亚企安数据恢复工程师简单聊一下基于linux的文件系统(EXT2/EXT3/EXT4/Reiserfs/Xfs) 下删除或者格式化的数据恢复流程和可行性。
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
下一篇
DataWorks