进程基础知识

目录

一、进程相关概念

进程和程序

虚拟内存和物理内存映射关系

​编辑进程控制块 PCB

环境变量 

常见环境变量

二、进程控制

fork函数

其他相关函数

循环创建n个子进程

进程共享

父子进程gdb调试

exec函数族

exec函数族特性

孤儿进程和僵尸进程!!!

wait回收子进程!!!

获取子进程退出值和异常终止信号

捕获程序异常终止的信号并打印

waitpid回收子进程

回收指定子进程

waitpid回收多个子进程

Demo

一、进程相关概念

进程和程序

程序:死的。只占用磁盘空间。(类似生活中的剧本)
进程;活的。运行起来的程序。占用内存、cpu等系统资源。(类似生活中的戏)
并发和并行:并行是宏观上并发,微观上串行

虚拟内存和物理内存映射关系

进程控制块 PCB

/usr/src/linux-headers-5.4.0-150-generic/include/linux/sched.h文件中可以查看 struct task_struct
结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
1、进程id
2、文件描述符表
3、进程状态: 初始态、就绪态、运行态、挂起态、终止态。
4、进程工作目录位置
5、信号相关信息资源。
6、用户id和组id
7、描述虚拟地址空间的信息

环境变量 

环境变量, 是指在操作系统中用来指定操作系统运行环境的一些参数。 通常具备以下特

常见环境变量

echo $PATH 查看环境变量
path环境变量里记录了一系列的值,当运行一个可执行文件时,系统会去环境变量记录的位置里查找这个文件并执行。
echo $TERM 查看终端
echo $LANG 查看语言
env 查看所有环境变量

获取环境变量值
char *getenv(const char *name); 
成功:返回环境变量的值;失败: NULL (name 不存在)

设置环境变量的值
int setenv(const char *name, const char *value, int overwrite); 成功: 0;失败:-1
参数 overwrite 取值: 1:覆盖原环境变量
0:不覆盖。 (该参数常用于设置新环境变量,如: ABC =haha-day-night)

删除环境变量 name 的定义
int unsetenv(const char *name); 成功: 0;失败: -1
注意事项: name 不存在仍返回 0(成功),当 name 命名为"ABC="时则会出错。

二、进程控制

fork函数

创建一个子进程。
pid_t fork(void); 
失败返回-1;成功返回:父进程返回子进程的ID(非负) 子进程返回 0
pid_t 类型表示进程 ID,但为了表示-1,它是有符号整型。 (0 不是有效进程 ID,
init 最小,为 1)
注意返回值,不是 fork 函数能返回两个值,而是 fork 后, fork 函数变为两个,父
子需【各自】返回一个。

小注释:PS ajx ——>可查看 pid ppid gid sid

其他相关函数

获取当前进程 ID
pid_t getpid(void);

获取当前进程的父进程 ID
pid_t getppid(void);

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

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

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

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

循环创建n个子进程

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

int main(int argc, char*arv[]){
    int i = 0;
    for(i = 0;i < 5;i++){
        if(fork() == 0)
            break;
    }

    if(5 == i){
        printf("I'm parent ID")
    }else{
        sleep(i);
        printf("I'm %dth children ID", i);
    }    
    return 0;
}

若不枝剪,则会造成如下情况:

从上图我们可以很清晰的看到, 当 n 为 3 时候,循环创建了(2^n)-1 个子进程,而不是 N
的子进程。需要在循环的过程,保证子进程不再执行 fork ,因此当(fork() == 0)时, 子进程
应该立即 break;才正确。

进程共享

父子进程之间在刚fork后
父子相同处: 全局变量、.data、.bbs、.text、栈、堆、环境变量、用户ID、宿主目录(进程用户家目录)、进程工作目录、信号处理方式等等,即0~3G的用户空间是完全一样的。
父子不同处: 1.进程ID   2.fork返回值   3.父进程ID    4.进程运行时间    5.闹钟(定时器)   6.未决信号集

父子进程间遵循读时共享写时复制的原则。
注意:只有进程空间的各段的内容要发生变化时(子进程或父进程进行写操作时,都会引起复制),才会将父进程的内容复制一份给子进程。在fork之后两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。 

父子进程共享: 1. 文件描述符(打开文件的结构体) 2. mmap 建立的映射区(进程间通信详解)

父子进程gdb调试

gdb调试:
设置父进程调试路径:set follow-fork-mode parent (默认)
设置子进程调试路径:set follow-fork-mode child
注意,一定要在fork函数调用之前设置才有效

exec函数族

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

#include <unistd.h>

加载一个进程, 通过 路径+程序名 来加载

int execl(const char *path, const char *arg, ...,NULL);
参1: 程序名
		参2: argv0
		参3: argv1
		...: argvN
		哨兵:NULL

加载一个进程,借助 PATH 环境变量              
int execlp(const char *file, const char *arg, ...,NULL);

例:execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在 PATH 中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数 1 给出的绝对路径搜索。
                    
int execle(const char *path, const char *arg, ...
                       
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char* argv[]){

    int fd;
    fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0664);

    dup2(fd, STDOUT_FILENO);
    execlp("ps","ps","ax",NULL);
    //close(fd);

    return 0;
}

exec函数族特性

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

l(list)

命令行参数列表

p(path)

搜索file时使用path变量

v(vector)

使用命令行参数数组

e(environment)

使用环境变量数组,不适用进程原有的环境变量,

设置新加载程序运行的环境变量

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

孤儿进程和僵尸进程!!!

孤儿进程:
父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。

僵尸进程:
子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。 kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。
子进程终止时,子进程残留资源PCB存放于内核中,PCB记录了进程结束原因,进程回收就是回收PCB。回收僵尸进程,得kill它的父进程,让孤儿院去回收它

wait回收子进程!!!

回收子进程退出资源, 阻塞回收任意一个。

#include <sys/types.h>
#include <sys/wait.h>
回收子进程退出资源, 阻塞回收任意一个子进程。
pid_t wait(int *wstatus);
参数:(传出) 回收进程的状态。
返回值:成功: 回收进程的pid
		失败: -1, errno

父进程调用 wait 函数可以回收子进程终止信息。 该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。

获取子进程退出值和异常终止信号

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

获取子进程正常终止值:

WIFEXITED(status) --> 为真 -->调用 WEXITSTATUS(status) --> 得到 子进程 退出值。

获取导致子进程异常终止信号:

WIFSIGNALED(status) --> 为真 -->调用 WTERMSIG(status) --> 得到 导致子进程异常终止的信号编号。

捕获程序异常终止的信号并打印

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

int main(int argc, char*argv[]){

    pid_t pid,wpid;
    int status;

    pid = fork();
    if(pid > 0){
        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));
        }
    }else if(pid == 0){
        printf("---child, my id = %d",getpid());
        sleep(10);
        printf("---------------die");
        return 73;

    }else if(pid ==-1){
        perror("fork error");
        exit(1);
     }
    return 0;
}

waitpid回收子进程

指定某一个进程进行回收。可以设置非阻塞
pid_t waitpid(pid_t pid, int *status, int options);
参数:
	pid:指定回收某一个子进程pid
		> 0: 待回收的子进程pid
		-1:任意子进程
		0:同组的子进程。
	status:(传出) 回收进程的状态。
	options:WNOHANG 指定回收方式为,非阻塞。
返回值:
	> 0 : 表成功回收的子进程 pid
	0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
	-1: 失败。errno
    < -1 回收指定进程组内的任意子进程

回收指定子进程

#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,tpid;

    for(i = 0;i < 5;i++){
        pid = fork();
        if(pid == 0){
            break;
        }
        if(2 == i){
            tpid = pid;
            printf("-----pid  = %d\n",tpid);
        }
    }

    if(5==i){
        sleep(5);
        wpid = waitpid(tpid,NULL,WNOHANG);
        if(wpid==-1){
           perror("wpid error");
           exit(1);
        }

        printf("I'm parent ,wait a child finish:%d\n",wpid);
    }else{
        sleep(i);
        printf("I'm %dth child pid:%d\n",i+1,getpid());
    }
    return 0;
}

waitpid回收多个子进程

一次wait/waitpid函数调用,只能回收一个子进程。

while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) {  //使用非阻塞方式,回收子进程.  
    if (wpid > 0) {  
        printf("wait child %d \n", wpid);  
    } else if (wpid == 0) {  
        sleep(1);  
        continue;  
    }  
} 

while ((wpid = waitpid(-1, NULL, 0)) != -1) {   //使用阻塞方式,回收子进程.  
    printf("wait child %d \n", wpid);  
} 

Demo

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

#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;
   int status;
   pid_t wpid;
   for(i = 0;i<3;i++){
        if(fork() == 0){
            break;
        }
   }

   if(i == 0){
        execlp("ps","ps","ax",NULL);
   }else if(i == 1){
        execl("./test","./test",NULL);
   }else if(i == 2){
        int a =1,b=2;
        printf("%d\n",a+b);
   }else if(i == 3){
        printf("I'm parent :%d\n",getpid());
        while((wpid = waitpid(-1,&status,WNOHANG))!=-1){
            if(wpid == 0){
                sleep(1);
                continue;
            }else if(wpid > 0){
                printf("finish a child:%d\n",wpid);
            }
        }
   }
   return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值