【ONE·Linux || 地址空间与进程控制(二)】

news2024/11/17 9:52:06

总言

  进程地址空间和进程控制相关介绍。

文章目录

  • 总言
  • 2、进程控制·续
    • 2.3、进程等待
      • 2.3.1、为什么需要进程等待
      • 2.3.2、阻塞式等待
        • 2.3.2.1、使用wait
        • 2.3.2.2、使用waitpid
        • 2.3.2.3、参数status基本介绍
      • 2.3.3、一些细节与问题</font>
        • 2.3.3.1、进程独立性说明
        • 2.3.3.1、父进程凭什么拿到子进程的数据
        • 2.3.3.3、堆栈内存泄漏和操作系统层面的内存泄漏差异性
      • 2.3.4、如何等待2.0(进一步细节展示+非阻塞式等待)
        • 2.3.4.1、参数说明2.0:pid、status相关宏设置
        • 2.3.4.2、waitpid第三参数options:非阻塞式等待设置处
        • 2.3.4.3、实操演示
    • 2.4、进程替换
      • 2.4.1、是什么
      • 2.4.2、怎么办1.0:execl函数演示
        • 2.4.2.1、execl程序替换:不创建子进程
        • 2.4.2.2、execl程序替换:创建子进程
      • 2.4.3、怎么办2.0:其它exec函数演示
        • 2.4.3.1、execv函数演示
        • 2.4.3.2、execlp\ececvp函数演示
        • 2.4.3.3、execle\execvpe函数演示
        • 2.4.3.4、ececve 系统调用接口

  
  

2、进程控制·续

2.3、进程等待

在这里插入图片描述

2.3.1、为什么需要进程等待

  1、子进程退出,父进程不管子进程,子进程就会进入僵尸状态。处于僵尸状态的子进程会导致内存泄露问题,那么,如何回收僵尸进程呢?此处就需要用到进程等待相关知识。
  2、我们学习父进程创建子进程,是为了让子进程办事。那么子进程把任务完成得怎样,父进程需要关系吗?如果要,它是如何得知的?如果不要,又该如何处理?此处也需要用到进程等待的知识。

  
  
  
  

2.3.2、阻塞式等待

2.3.2.1、使用wait

  1)、wait :基本验证,回收僵尸进程

在这里插入图片描述先对僵尸进程简单回顾(详细可以看进程概念那一篇博客)

  这是用于观察的脚本代码:

while :; do ps axj | head -1 && ps axj | grep test.out | grep -v grep; sleep 1 ; echo "------------------------------------------------------------"; done

  下述为僵尸状态的演示代码:

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

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);  
        }
			exit(0);//终止子进程  
    }
    else{
        while(1)
        {
            printf("I am parent, pid:%d ,ppid:%d \n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述
  
  
  

在这里插入图片描述使用wait回收僵尸进程演示

  该函数基本介绍:

在这里插入图片描述
在这里插入图片描述

  该函数的返回值:
在这里插入图片描述

  使用演示:

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

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        exit(0);//终止子进程

    }
    else{

        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(7);
        pid_t ret=wait(NULL);//阻塞式等待
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d\n",ret);
        }
        //使用wait:可以不用让父进程做死循环等待了
        while(1)
        {
            printf("I am parent, pid:%d ,ppid:%d \n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述
  往后我们编写多进程时,以便都是采用fork+wait/waitpid的方法。
  
  
  

2.3.2.2、使用waitpid

  1)、waitpid :获取子进程退出结果

在这里插入图片描述waitpid简单介绍

pid_ t waitpid(pid_t pid, int *status, int options);

在这里插入图片描述

  
  2)、waitpid 演示一:阻塞式等待

在这里插入图片描述waitpid(pid, NULL , 0) ,等价于使用 wait(NULL)

  先简单演示一下waitpid的基本使用方法:

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

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        exit(0);//终止子进程

    }
    else{

        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(7);
        pid_t ret=waitpid(id,NULL,0);//注意此处父进程获取得的id即子进程id,详细请了解fork返回值。
        //pid_t ret=wait(NULL);//阻塞式等待
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d\n",ret);
        }
        //使用wait:可以不用让父进程做死循环等待了
        while(1)
        {
            printf("I am parent, pid:%d ,ppid:%d \n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述
  
  
  

2.3.2.3、参数status基本介绍

  1)、问题引入

waitpid(pid, &status , 0)  //status为输出型参数,可用于获取子进程退出时的状态信息

  演示代码如下:我们将子进程退出码设置为自己意属的值,而后通过status获取该值来观测结果。

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

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        exit(111);//终止子进程,设置子进程退出码

    }
    else{

        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        //sleep(7);
        int status=0;
        pid_t ret=waitpid(id,&status,0);//注意此处父进程获取得的id即子进程id,详细请了解fork返回值。
        //pid_t ret=wait(NULL);//阻塞式等待
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d,status:%d\n",ret,status);
        }

    }
    return 0;
}

  待程序执行完成,我们获得status的值,却发现该值与我们与其所设想的不一致。

[wj@VM-4-3-centos t1113]$ ./test.out
I am parent, pid:22927, ppid:4925
cnt:5 , I am child, pid:22928, ppid:22927 
cnt:4 , I am child, pid:22928, ppid:22927 
cnt:3 , I am child, pid:22928, ppid:22927 
cnt:2 , I am child, pid:22928, ppid:22927 
cnt:1 , I am child, pid:22928, ppid:22927 
wait child process successfully!,ret:22928,status:28416//可看到此处status的返回值是一个很大的数值

  这是什么原因呢? 我们下面将提及。
  关于阻塞式等待父子进程顺序说明: 文中提到的代码有一个特点,即只有子进程退出的时候,父进程才会使用waitpid/wait函数进行返回,即父进程在子进程之后仍旧活着。
  这说明 waitpid/wait目前情况下可以让进程具有一定的顺序性 ,将来我们也可以让父进程进行更多的收尾工作。
  
  
  

  2)、status详细介绍

在这里插入图片描述0、综述:

在这里插入图片描述

  
  
  

在这里插入图片描述1、进程正常终止,status的次低8位表示进程退出码

  为了获取该码,我们可以做以下变化:

(status>>8)&0XFFF

  先将status得到的值右移8位,这样次低8位的值就到了最低8位上,然后我们让按位与上0XFF(0000 0000 1111 1111),这样status更高位的数值就都为0了。
  现在我们来看一看结果:

[wj@VM-4-3-centos t1113]$ ./test.out
I am parent, pid:557, ppid:31111
cnt:5 , I am child, pid:558, ppid:557 
cnt:4 , I am child, pid:558, ppid:557 
cnt:3 , I am child, pid:558, ppid:557 
cnt:2 , I am child, pid:558, ppid:557 
cnt:1 , I am child, pid:558, ppid:557 
wait child process successfully!,ret:558,status:111 //status打印的值确实如我们设置一般
[wj@VM-4-3-centos t1113]$ 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        exit(111);//终止子进程

    }
    else{

        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        //sleep(7);
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d,status:%d\n",ret,(status>>8)&0XFF);
        }

    return 0;
}

  
  
  
  

在这里插入图片描述2、进程异常退出,status最低7位表示进程收到的型号

  一个前提认知:进程异常退出或者崩溃,本质上是操作系统杀掉了该进程。
  操作系统如何操作这个过程呢?本质是通过发送信号的方式。
  为了获取相关信号,我们对原先实验的代码做如下改动并验证:

(status&0X7F)
OX7F: 0000 0000 0111 1111

  发送的信号为0,说明进程正常终止。
  此时,根据我们之前学的进程终止三情形,我们可以通过后面的status的返回值来判断它是正常跑完终止后的哪种状态。

[wj@VM-4-3-centos t1113]$ ./test.out
I am parent, pid:5662, ppid:31111
cnt:5 , I am child, pid:5663, ppid:5662 
cnt:4 , I am child, pid:5663, ppid:5662 
cnt:3 , I am child, pid:5663, ppid:5662 
cnt:2 , I am child, pid:5663, ppid:5662 
cnt:1 , I am child, pid:5663, ppid:5662 
wait child process successfully!,ret:5663,signal:0, status:111
[wj@VM-4-3-centos t1113]$ 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        exit(111);//终止子进程

    }
    else{
        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        //sleep(7);
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d,signal:%d, status:%d\n",ret,(status&0X7F),(status>>8)&0XFF);
        }
    return 0;
}

  接下来,我们使用一个子进程异常情况来实验:

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

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//表示进程运行完毕,结果不正确
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
            //以下为使子进程异常的演示代码:
            int a=10;
            a /=0;
        }
        exit(111);//终止子进程

    }
    else{

        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        //sleep(7);
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)
        {
            printf("wait child process successfully!,ret:%d,signal:%d, status:%d\n",ret,(status&0X7F),(status>>8)&0XFF);
        }
    }
    return 0;
}

在这里插入图片描述
  需要注意的是,进程异常不仅仅只体现在内部代码有问题上,当我们外力人为直接杀掉进程, 也算作进程异常。比如我们之前执行kill -9 +子进程pid时:
在这里插入图片描述
  
  
  
  

2.3.3、一些细节与问题

2.3.3.1、进程独立性说明

在这里插入图片描述问题一:
  已知父进程通过wait/waitpid可以拿到子进程的退出结果。既然如此,我们为什么要用wait/waitpid函数呢?直接定义一个全局变量来完成它不是更加简洁吗?

  相关代码:

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

int code=0;//定义一个全局变量

int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);
    }
    else if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("cnt:%d , I am child, pid:%d, ppid:%d \n",cnt,getpid(),getppid());
            cnt--;
            sleep(1);
        }
        code=111; //在子进程处直接设置全局变量
        exit(111);

    }
    else{
        printf("I am parent, pid:%d, ppid:%d\n",getpid(),getppid());
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)
        {
            printf("code:%d\n",code);//在父进程处读取该值
            printf("wait child process successfully!,ret:%d,signal:%d, status:%d\n",ret,(status&0X7F),(status>>8)&0XFF);
        }
    return 0;
}

  
  结果如下:

[wj@VM-4-3-centos t1113]$ ./test.out
I am parent, pid:11689, ppid:10835
cnt:5 , I am child, pid:11690, ppid:11689 
cnt:4 , I am child, pid:11690, ppid:11689 
cnt:3 , I am child, pid:11690, ppid:11689 
cnt:2 , I am child, pid:11690, ppid:11689 
cnt:1 , I am child, pid:11690, ppid:11689 
code:0
wait child process successfully!,ret:11690,signal:0, status:111
[wj@VM-4-3-centos t1113]$ 

  可以看到code读取失败直接为0,这是因为父子进程代码具有独立性,在子进程中修改code值时发生了写时拷贝。故不能用此法来获取子进程退出状态。
  
  
  
  

2.3.3.1、父进程凭什么拿到子进程的数据

在这里插入图片描述问题二:
  既然进程具有独立性,进程退出码不也是子进程独有的数据吗。那么父进程凭什么拿到子进程的数据?wait/waitpid在这期间扮演什么角色做了什么事?

  回答:
  1、首先,从僵尸进程谈起。僵尸进程退出后,至少会保留该进程的PCB信息。即task_struct里面保留了任何进程退出时的退出结果信息。
  2、wait、waitpid本质上是在读取子进程tast_struct结构体内的数据信息。
  3、wait、waitpid属于系统调用函数,本质上是操作系统本身进行处理,因此其有权限拿到task_struct该内核结构对象的数据
在这里插入图片描述
  
  
  
  

2.3.3.3、堆栈内存泄漏和操作系统层面的内存泄漏差异性

在这里插入图片描述问题三:
  假如我们创建了一个进程,在该进程中使用了malloc或new一块空间,之后进程退出,请问在没有free或delete的情况下,该内存是否会泄露?和子进程的僵尸状态带来的内存泄露有什么区别?

  回答:对于前者,属于用户在堆区申请的空间,进程退出后不会造成内存泄漏,因为操作系统会自动回收该空间。对于后者,这属于操作系统层面的内存泄漏
  
  
  
  

2.3.4、如何等待2.0(进一步细节展示+非阻塞式等待)

2.3.4.1、参数说明2.0:pid、status相关宏设置

  1)、waitpid参数介绍一:pid
  在之前我们只是简单的介绍了waitpid的基本使用方法,对于其参数pid我们直接使用了fork返回的id值,此处需要注意:
  id > 0 时,我们等待的是指定进程
  id== -1 时,我们等待的是任意一个子进程,等价于wait( )接口。
  
  
  2)、直接获取stauts:使用系统提供的宏

WIFEXITED(status): (查看进程是否是正常退出),若为真,则表示正常终止子进程返回的状态。
WEXITSTATUS(status): (查看进程的退出码),若WIFEXITED非零,提取子进程退出码。

  两个宏的记忆方法:W/IF/EXITEDW/EXIT/STATUS
  
  
  演示代码一:

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

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        exit(1);
    }
    else if(id==0)//子进程
    {
        int cen=5;
        while(cen)
        {
            printf("I am child:%d\n",cen--);
            sleep(1);
        }
        exit(111);
    }
    else //父进程
    {
        int status=0;
        pid_t result=waitpid(id,&status,0);//阻塞等待子进程状态变化
        if(result>0)
        {
            if(WIFEXITED(status))
            {
                //父进程等待成功
                printf("子进程执行完毕,子进程退出码:%d\n",WEXITSTATUS(status));
            }
            else 
            {
                printf("子进程异常退出:%d\n",WIFEXITED(status));//此处是在子进程异常退出时打印看看状态
            }
        }

    }
    return 0;
}

  执行结果:可看到WIFEXITED(status)执行为真,后续将退出码直接打印出来。
  这样子的一个好处是,我们可以不用了解具体的操作内核去做位移转换运算,直接通过使用系统提供的宏来获取子进程退出信息。

[wj@VM-4-3-centos t1113]$ ./test.out
I am child:5
I am child:4
I am child:3
I am child:2
I am child:1
子进程执行完毕,子进程退出码:111

  
  

2.3.4.2、waitpid第三参数options:非阻塞式等待设置处

  1)、options参数简单介绍
  根据上述几个演示代码,可以看到阻塞式等待中,父进程只在做一件事,即处于阻塞状态中,等待子进程状态发生改变。这样使用父进程有些大材小用,因此延伸出了让父进程既能等待子进程,同时也能处理其它任务的操作方法:
  这就需要学习waitpid的第三参数:options
  1、options== 0,默认为阻塞等待
  2、options== WNOHANG,代表父进程为非阻塞等待

  PS:WNOHANG是系统提供的一个宏(定义成宏的原因:魔鬼数字)

在这里插入图片描述
  相关记忆方式:W/NO/HANG
  
  
  2)、关于阻塞式等待、非阻塞式等待相关理解说明
在这里插入图片描述
  如上述,简单举例了系统调用函数waitpid相关实现框架,根据第三参数options来判断进程等待方式。
  对于阻塞式等待,父进程不会执行后续代码操作,这是由于父进程在系统内部被挂起,即父进程的pcb(进程控制块)被放入等待队列中,其结果为进程阻塞在系统函数内部,只有当条件满足的时候,父进程被唤醒(相关进程控制块重新加载到CPU中被执行),而后接着执行。注意:这里waitpid重新调用时,是通过EIP寄存器来判断上此切换出的相关代码位置,并非重新执行所有代码。
  
  对于非阻塞等待,使用waitpid后,waitpid判断子进程没有退出后会直接返回,换句话说,非阻塞式等待是通过轮询检测的方案来实现一次次调用的。
  
  
  3)、相关意义说明
  比如scanf、cin等,虽然上层看是通过语言实现的,但其内部使用了相关的系统调用接口,实际上,网络代码中大部分为IO类别,它们会不断面临阻塞与非阻塞接口。
  
  
  
  

2.3.4.3、实操演示

  1)、基础演示一:
  以下把如何用非阻塞式等待进行轮询检测的相关框架列举出来,代码如下:

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

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        int count=5;
        while(count)
        {
            printf("I am child:%d\n",count--);
            sleep(1);
        }
        exit(9);//测试:此处数字无具体含义
    }
    else //父进程:演示非阻塞式等待,轮循检测
    {
        int quit=0;//类似flag,用于标记何时退出
        while(!quit)
        {
            int status=0;
            pid_t ret=waitpid(-1,&status,WNOHANG);
            if(ret>0)//等待成功+子进程退出
            {
                printf("等待子进程退出成功,退出码为:%d\n",WEXITSTATUS(status));
                quit=1;
            }
            else if(ret==0)//等待成功+子进程尚未退出
            {
                printf("子进程尚在运行中暂时未退出,此时父进程可处理其它事件\n");
                //……
                //这里可以写父进程要处理的内容
            }
            else//waitpid等待失败 
            {
                printf("wait失败。\n");
                quit=1;
            }
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述

  
  
  2)、基础演示二:
  我们将上述演示一中,父进程待处理事件完善一下,做一个简单示范:

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

typedef void (*Handler_t)();//函数指针类型
std::vector<Handler_t> handlers;//函数指针数组

//待处理的临时任务:这是在非阻塞等待时,交给父进程的任务
void fun1()
{
    printf("待处理临时任务1\n");
}

void fun2()
{
    printf("待处理临时任务2\n");
}

//Load函数:可用于注入待处理任务,将其设置为Load可做到切块处理,便于更改
void Load()
{
    handlers.push_back(fun1);
    handlers.push_back(fun2);
}

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        int count=5;
        while(count)
        {
            printf("I am child:%d\n",count--);
            sleep(1);
        }
        exit(9);//测试:此处数字无具体含义
    }
    else //父进程:演示非阻塞式等待,轮循检测
    {
        int quit=0;//类似flag,用于标记何时退出
        while(!quit)
        {
            int status=0;
            pid_t ret=waitpid(-1,&status,WNOHANG);
            if(ret>0)//等待成功+子进程退出
            {
                printf("等待子进程退出成功,退出码为:%d\n",WEXITSTATUS(status));
                quit=1;
            }
            else if(ret==0)//等待成功+子进程尚未退出
            {
                printf("子进程尚在运行中暂时未退出,此时父进程可处理其它事件\n");
                //…
                if(handlers.empty())
                    Load();
                for(auto iter:handlers)
                {
                    //执行相关任务
                    iter();
                }
            }
            else//waitpid等待失败 
            {
                printf("wait失败。\n");
                quit=1;
            }
            sleep(1);
        }
    }
    return 0;
}

  
  
  

2.4、进程替换

在这里插入图片描述

2.4.1、是什么

  1)、问题引入:fork的两种用法说明
  在前文,我们介绍了fork的一种用法,实际上fork通常有两种常见用法:
  ①一个父进程希望复制自己,使父子进程同时执行不同的代码段。此时,父子进程代码相同,数据写时拷贝各自一份。
  ②一个进程要执行一个不同的程序。此时父子进程代码不同,数据也不同。
  那么有没有相关实现方法?这里就要用到进程替换。
  
  
  
  2)、进程替换介绍
  进程替换: 通过特定接口,加载磁盘上的一个权限的程序(包括代码和数据),加载到调用进程的地址空间中,让子进程执行相关程序。

在这里插入图片描述
  说明:事实上,操作系统为我们提供了这类函数,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行
  
  
  
  
  3)、细节理解
  问题一:进程替换,有没有创建新的进程?
  回答:调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
  
  
  
  问题二:如何理解将程序放入内存中?
  回答:实际指将程序加载到内存中,和当前进程页表建立映射关系,可通过操作系统相关接口调用完成,即接下来要介绍的exec系列函数。
  
  
  
  
  

2.4.2、怎么办1.0:execl函数演示

2.4.2.1、execl程序替换:不创建子进程

  1)、函数介绍
  man execl可查询进程替换相关函数。

在这里插入图片描述

  path为对应程序所在路径,arg, ...是可变参数列表,可以传入多个不定个数参数,但其最后一个参数必须传递NULL,表示参数传递完毕。

       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 execvpe(const char *file, char *const argv[],char *const envp[]);

  
  
  2)、基础演示
  这里以execl函数为例,演示不创建子进程时,如何进行程序替换:需要注意,这里path的参数为const char *,我们传入的是字符串。

在这里插入图片描述
  
  相关代码:

在这里插入图片描述演示一:

[wj@VM-4-3-centos T0714]$ ll
total 40
-rw-rw-r-- 1 wj wj  155 Jul 14 19:25 makefile
-rw-rw-r-- 1 wj wj 1236 Jul 14 12:27 proc01.c
-rwxrwxr-x 1 wj wj 8616 Jul 14 12:28 proc01.out
-rw-rw-r-- 1 wj wj  166 Jul 14 19:23 proc02.c
-rw-rw-r-- 1 wj wj 1962 Jul 14 13:02 proc02.cpp
-rwxrwxr-x 1 wj wj 8360 Jul 14 19:25 proc02.out

[wj@VM-4-3-centos T0714]$ ./proc02.out
当前进程开始:
......
当前进程结束
[wj@VM-4-3-centos T0714]$ cat proc02.c
#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("当前进程开始:\n");
    printf("......\n");
    printf("当前进程结束\n");
    return 0;
}

  
  

在这里插入图片描述演示二:

[wj@VM-4-3-centos T0714]$ ll
total 40
-rw-rw-r-- 1 wj wj  155 Jul 14 19:25 makefile
-rw-rw-r-- 1 wj wj 1236 Jul 14 12:27 proc01.c
-rwxrwxr-x 1 wj wj 8616 Jul 14 12:28 proc01.out
-rw-rw-r-- 1 wj wj  201 Jul 14 19:31 proc02.c
-rw-rw-r-- 1 wj wj 1962 Jul 14 13:02 proc02.cpp
-rwxrwxr-x 1 wj wj 8408 Jul 14 19:31 proc02.out

[wj@VM-4-3-centos T0714]$ ./proc02.out
当前进程开始:
makefile  proc01.c  proc01.out	proc02.c  proc02.cpp  proc02.out
[wj@VM-4-3-centos T0714]$ cat proc02.c
#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("当前进程开始:\n");
    execl("/usr/bin/ls","ls",NULL);
    printf(".....\n");
    printf("当前进程结束\n");
    return 0;
}
[wj@VM-4-3-centos T0714]$ which ls
alias ls='ls --color=auto'
	/usr/bin/ls

  
  

在这里插入图片描述演示三:

[wj@VM-4-3-centos T0714]$ make
g++ -o proc02.out proc02.c
[wj@VM-4-3-centos T0714]$ ./proc02.out
当前进程开始:
total 48
drwxrwxr-x 2 wj wj 4096 Jul 14 19:37 .
drwxrwxr-x 4 wj wj 4096 Jul 14 12:06 ..
-rw-rw-r-- 1 wj wj  155 Jul 14 19:25 makefile
-rw-rw-r-- 1 wj wj 1236 Jul 14 12:27 proc01.c
-rwxrwxr-x 1 wj wj 8616 Jul 14 12:28 proc01.out
-rw-rw-r-- 1 wj wj  245 Jul 14 19:37 proc02.c
-rw-rw-r-- 1 wj wj 1962 Jul 14 13:02 proc02.cpp
-rwxrwxr-x 1 wj wj 8408 Jul 14 19:37 proc02.out

[wj@VM-4-3-centos T0714]$ cat proc02.c
#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("当前进程开始:\n");
    //execl("/usr/bin/ls","ls",NULL);
    execl("/usr/bin/ls","ls","-al",NULL);
    printf(".....\n");
    printf("当前进程结束\n");
    return 0;
}

  
  
  
  3)、相关说明
  问题一:如何理解使用execl后不再执行后续代码?
  回答: exec系列函数如果调用成功,则会将当前进程中的所有代码和数据统统替换,包括已执行的和未执行的。
  以下述代码为例做解释: 使用execl后,进程内部代码数据在这里替换为ls,后续printf(".....\n");printf("当前进程结束\n");将不会被执行,而execl前的printf("当前进程开始:\n");也会被一并替换,只是它在替换前已经被执行。

int main()
{
    printf("当前进程开始:\n");
    execl("/usr/bin/ls","ls",NULL);
    printf(".....\n");
    printf("当前进程结束\n");
    return 0;
}

  
  
  问题二:关于execl返回值。为什么调用成功没有返回值?
  回答: 原因同上。该系列函数,若调用成功,则加载新的程序并从启动代码开始执行,不再返回。如果调用出错则返回-1。所以exec函数只有出错的返回值,而没有成功的返回值。
  举例: 实际使用时,可根据需要在execl后加上exit,一旦程序在此处退出,意味着进程替换执行失败。

int main()
{
    printf("当前进程开始:\n");
    execl("/usr/bin/ls","ls",NULL);
    exit(-1);//表示进程替换失败。
    printf(".....\n");
    printf("当前进程结束\n");
    return 0;
}

  
  
  
  
  

2.4.2.2、execl程序替换:创建子进程

   1)、为什么要有创建子进程的替换方式?

  问题说明: 在上述中,我们直接使用了execl系列,可实现将当前进程进行替换,那么为什么还需要额外创建子进程来完成此项任务?

  从需求角度考虑: 若原先父进程原先代码为刚需,要求不能更动(例如后续还需要执行、用到),那么创建子进程用于进程替换可以保证不影响父进程的同时,替换上新的代码数据。(PS:进程具有独立性,因此只会替换掉子进程的代码数据段,不会把父进程的一并替换)
  从分工角度考虑: 让父进程聚焦在读取数据、解析数据上,指派进程执行代码功能。
  
  
   2)、如何操作?

  以下为一个演示案例: 这里我们以ls -a -l为例。

[wj@VM-4-3-centos T0715]$ ls -a -l
total 32
drwxrwxr-x 2 wj wj 4096 Jul 15 14:57 .
drwxrwxr-x 5 wj wj 4096 Jul 15 11:22 ..
-rw-rw-r-- 1 wj wj   73 Jul 15 12:20 makefile
-rwxrwxr-x 1 wj wj 8616 Jul 15 14:57 test01
-rw-rw-r-- 1 wj wj  982 Jul 15 14:57 test01.c
-rw-rw-r-- 1 wj wj 1130 Jul 15 14:54 test02.c
[wj@VM-4-3-centos T0715]$ which ls
alias ls='ls --color=auto'
	/usr/bin/ls

  
  演示结果如下:

execl("/usr/bin/ls","ls","-a","-l",NULL);

在这里插入图片描述
  
  总览:

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

#define NUM 16

int main(int argc, char *argv[], char *env[])
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        printf("子进程开始运行,pid:%d\n",getpid());
        //在子进程中使用进程替换
        //ls -a -l
        execl("/usr/bin/ls","ls","-a","-l",NULL);
        exit(-2);//进程替换失败
    }
    else//父进程 
    {
        printf("父进程开始运行,pid:%d\n",getpid());
        //让父进程阻塞式等待子进程:子进程运行完毕,父进程获取后才退出,保证运行时的顺序(PS:单独fork,父子进程顺序具有不确定性)
        int status=0;
        pid_t ret=waitpid(-1,&status,0);//参数:等待任意一个子进程、获取进程状态、阻塞式等待
        if(ret)
            printf("wait success, exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

  关于为什么需要进程等待说明: 实际上这里的顺序性涉及到后续实际运用。我们可以结合上述1)中内容来理解,假父进程为总指挥官,旗下有各子进程作为执行者,则父进程需要不断下达子任务,委派子进程执行并将结果反馈。那么,单独使用fork时,由于CPU调度,无法确保谁先执行完成退出,加入进程等待,可以保证父进程在子进程之后退出。
  故而上述演示案例,我们也可以嵌套一层while(1)循环,保证父进程时刻运行。

int main(int argc, char*argv[], char *env[])
{
    while(1)
    {
        pid_t id = fork();
        if(id == 0)
        {
            //子进程
            //……进程替换
        }
        else 
        {
            //父进程
            //……
        }
    } //end while(1)
    return 0;
}

  
  
  
  
  3)、进程替换中,父子进程代码和数据的关系解释
  根据先前所学,fork之后,父子进程代码共享,数据写时拷贝
  这里引入进程替换,当新程序加载之前,fork后父子进程仍旧是代码共享,数据写时拷贝。但当子进程加载新的程序时,相当于一种写入,此时子进程的代码也要进行写时拷贝。
  由此说明,进程替换后,父子进程代码、数据都进行写时拷贝
  
  
  
  
  

2.4.3、怎么办2.0:其它exec函数演示

   1)、exec系列函数再展示

       #include <unistd.h>

       extern char **environ;

       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 execvpe(const char *file, char *const argv[],
                   char *const envp[]);

  关于上述几个函数的记忆与理解:

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

在这里插入图片描述

  
  

2.4.3.1、execv函数演示

  函数声明如下:

int execv(const char *path, char *const argv[]);

  基本说明:
  这些系列函数的使用大体无区别,只是在细节上做一定修改。比如这里的execv,根据第二参数char *const argv[],需要传递的是指针数组,因此这里我们建立一个数组用于传递相关指令:

#define NUM 16

char* const _argv[NUM]={(char*)"ls",(char*)"-a",(char*)"-l",NULL};
execv("/usr/bin/ls",_argv);//注意路径写法

  
  
  演示结果如下:
在这里插入图片描述

  
  相关代码如下:

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

#define NUM 16


//int main(int argc, char *argv[], char *env[])
int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        printf("子进程开始运行,pid:%d\n",getpid());
        //在子进程中使用进程替换
        //ls -a -l
       
        char *const _argv[NUM] = {
            (char*)"ls",(char*)"-a",(char*)"-l",(char*)"-i", NULL};
        execv("/usr/bin/ls", _argv);
        //char *const argv[] = {"ps", "-ef", NULL};
        //execv("/bin/ps",argv);

        //execl("/usr/bin/ls","ls","-a","-l",NULL);
        exit(-2);//进程替换失败
    }
    else//父进程 
    {
        printf("父进程开始运行,pid:%d\n",getpid());
        //让父进程阻塞式等待子进程:子进程运行完毕,父进程获取后才退出,保证运行时的顺序(PS:单独fork,父子进程顺序具有不确定性)
        int status=0;
        pid_t ret=waitpid(-1,&status,0);//参数:等待任意一个子进程、获取进程状态、阻塞式等待
        if(ret)
            printf("wait success, exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

  
  补充: 也可以加上颜色编辑:
在这里插入图片描述

  
  
  
  

2.4.3.2、execlp\ececvp函数演示

  函数声明如下:

int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);

  p(path) : 有p自动搜索环境变量PATH。因此这里的const char *file若是环境变量PATH中的程序,可不用写绝对路径,能够直接搜索到。
  
  
  相关演示:

	execlp("ls","ls","-a","-l",NULL);
    char *const _argv[NUM] = {
         (char*)"ls",(char*)"--color=auto",(char*)"-a",(char*)"-l",(char*)"-i", NULL};
    execvp("ls",_argv);

  
  
  演示结果如下:
在这里插入图片描述

  
  
  相关代码如下:

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

#define NUM 16


//int main(int argc, char *argv[], char *env[])
int main(void)
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        printf("子进程开始运行,pid:%d\n",getpid());
        //在子进程中使用进程替换
        //ls -a -l
       
        //execlp("ls","ls","-a","-l",NULL);

        char *const _argv[NUM] = {
            (char*)"ls",(char*)"--color=auto",(char*)"-a",(char*)"-l",(char*)"-i", NULL};
        execvp("ls",_argv);
        //execv("/usr/bin/ls", _argv);
    
        //char *const argv[] = {"ps", "-ef", NULL};
        //execv("/bin/ps",argv);

        //execl("/usr/bin/ls","ls","-a","-l",NULL);
        exit(-2);//进程替换失败
    }
    else//父进程 
    {
        printf("父进程开始运行,pid:%d\n",getpid());
        //让父进程阻塞式等待子进程:子进程运行完毕,父进程获取后才退出,保证运行时的顺序(PS:单独fork,父子进程顺序具有不确定性)
        int status=0;
        pid_t ret=waitpid(-1,&status,0);//参数:等待任意一个子进程、获取进程状态、阻塞式等待
        if(ret)
            printf("wait success, exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

  
  
  
  

2.4.3.3、execle\execvpe函数演示

  1)、如何执行我们自己写的C、C++二进制程序、其它语言的程序

在这里插入图片描述对我们自己写的C、C++程序

  说明:需要写明程序所在路径。

  演示结果如下:
在这里插入图片描述
  
  相关代码:
  test02.c中:

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

#define NUM 16
//const char *filepath="/home/wj/one.-studybylinux/study2023/T2307/T0715/proc02.out";
const char *filepath="./proc02.out";

int main(int argc, char *argv[], char *env[])
{
    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        printf("子进程开始运行,pid:%d\n",getpid());
        execlp(filepath,"proc02.out","-b",NULL);
        exit(-2);//进程替换失败
    }
    else//父进程 
    {
        printf("父进程开始运行,pid:%d\n",getpid());
        //让父进程阻塞式等待子进程:子进程运行完毕,父进程获取后才退出,保证运行时的顺序(PS:单独fork,父子进程顺序具有不确定性)
        int status=0;
        pid_t ret=waitpid(-1,&status,0);//参数:等待任意一个子进程、获取进程状态、阻塞式等待
        if(ret)
            printf("wait success, exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

  proc02.c中:

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

int main(int argc,char*argv[])
{
    if(argc !=2)
    {
        printf("can not exectue!\n");
        exit(1);
    }

    if(strcmp(argv[1],"-a")==0)
    {
        printf("hello a!\n");
    }
    else if(strcmp(argv[1],"-b")==0)
    {
        printf("hello b!\n");
    }
    else 
    {
        printf("default!\n");
    }
    return 0;
}

  makefile中:

.PHONY:all
all:test02.out proc02.out
	
test02.out:test02.c
	gcc -o $@ $^
proc02.out:proc02.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -rf *.out

  
  
  

在这里插入图片描述对其它语言的程序

  方法同上,只要有相关运行脚本即可。实际上exec系列函数功能类似于加载器。

execlp("./test.py", "test.py", NULL);
execlp("bash", "bash", "test.sh", NULL);
execlp("python", "python", "test.py", NULL);

  
  
  
  
  2)、带e的exec系列函数,需要自己组装环境变量
  相关演示如下:
在这里插入图片描述
  
  相关代码:

  在test02.c中设置一个环境变量,char *const _env[NUM],使用execle进程替换。

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

#define NUM 16
//const char *filepath="/home/wj/one.-studybylinux/study2023/T2307/T0715/proc02.out";
const char *filepath="./proc02.out";

int main(int argc, char *argv[], char *env[])
{   
    //设置一个环境变量:fork后子进程能继承父进程的环境变量
    char *const _env[NUM]={(char*)"MY_ENV=2233445566",NULL};

    pid_t id=fork();
    if(id<0)
    {
        return -1;
    }
    else if(id==0)//子进程
    {
        printf("子进程开始运行,pid:%d\n",getpid());
        execle(filepath,"proc02.out","-a",NULL,env);
        //execle(filepath,"proc02.out","-a",NULL,_env);
        //execlp(filepath,"proc02.out","-b",NULL);
        exit(-2);//进程替换失败
    }
    else//父进程 
    {
        printf("父进程开始运行,pid:%d\n",getpid());
        //让父进程阻塞式等待子进程:子进程运行完毕,父进程获取后才退出,保证运行时的顺序(PS:单独fork,父子进程顺序具有不确定性)
        int status=0;
        pid_t ret=waitpid(-1,&status,0);//参数:等待任意一个子进程、获取进程状态、阻塞式等待
        if(ret)
            printf("wait success, exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

  proc02.c,形成proc02.out文件,让子进程替换上:

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

int main(int argc,char*argv[])
{
    if(argc !=2)
    {
        printf("can not exectue!\n");
        exit(1);
    }
	
    printf("获取环境变量为:MY_ENV:%s\n",getenv("MY_ENV"));

    if(strcmp(argv[1],"-a")==0)
    {
        printf("hello a!\n");
    }
    else if(strcmp(argv[1],"-b")==0)
    {
        printf("hello b!\n");
    }
    else 
    {
        printf("default!\n");
    }
    return 0;
}

  
  意义说明:

execle(myfile, "mycmd", "-a", NULL, env);

  我们知道main函数有环境变量参数char *env[]。假设该main函数是父进程的,使用上述进程替换函数,就可以直接通过父进程的env,在子进程中获取到相应的环境变量。

int main(int argc, char *argv[], char *env[])
{
	//子进程
	
	//父进程   
}

  
  
  
  

2.4.3.4、ececve 系统调用接口

int execve(const char *path, char *const argv[], char *const envp[]);

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

  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/757050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络安全带你练爬虫-100练】第13练:文件的创建、写入

目录 目标&#xff1a;将数据写入到文件中 网络安全O 目标&#xff1a;将数据写入到文件中 开干 &#xff08;始于颜值&#xff09;打开一个&#xff0c;没有就会创建 with open(data.csv, modew, newline) as file: &#xff08;忠于才华&#xff09;开始写入数据 writer cs…

LinuxC/C++开发工具——make/makefile和gdb

linux开发工具 前言Linux项目自动化构建工具&#xff08;make/makefile&#xff09;makefile文件的组成如何使用make.PHONY关键字 项目清理 gdb调试器背景使用list&#xff08;l&#xff09;调试命令break&#xff08;b&#xff09;&#xff1a;设置断点info break&#xff1a;…

[STL] vector 模拟实现详解

目录 一&#xff0c;准备工作 二&#xff0c;push_back 1&#xff0c; 关于引用 2. 参数const 的修饰 补充 三&#xff0c;迭代器实现 四&#xff0c;Pop_back 五&#xff0c;insert 1. 补充——迭代器失效 六&#xff0c; erase 七&#xff0c;构造函数 1. 迭代…

合并当天Log

1.原因&#xff0c; 我们程序运行Log很多时&#xff0c;如果因为要写Log话费很多时间&#xff0c;这时我们可以把log保存按照更短的时间保存&#xff0c;比如一分钟一个Log,一个小时一个log&#xff0c;。。。。但我们查看Log时很麻烦&#xff0c;需要把分散的Log合并起来的工…

移动端深度学习部署:TFlite

1.TFlite介绍 &#xff08;1&#xff09;TFlite概念 tflite是谷歌自己的一个轻量级推理库。主要用于移动端。 tflite使用的思路主要是从预训练的模型转换为tflite模型文件&#xff0c;拿到移动端部署。 tflite的源模型可以来自tensorflow的saved model或者frozen model,也可…

ylb-定时任务task

总览&#xff1a; 在api模块service包&#xff0c;创建IncomeService类&#xff1a;&#xff08;收益计划 和 收益返还&#xff09; package com.bjpowernode.api.service;public interface IncomeService {/*收益计划*/void generateIncomePlan();/*收益返还*/void generate…

基于mysql+java+springboot的福州大学生就业求职系统(含源码+系统演示视频)

1、系统演示视频&#xff1a;基于JavaMySQLspringboot的福州大学生就业求职系统演示视频 2、系统源码&#xff1a;系统源码链接 文章目录 一、需求分析1、公司招聘2、简历管理3、交流咨询 二、福州大学就业求职服务平台简介1.福州大学就业求职服务平台主要功能1.1.个人求职功能…

小黑子—JavaWeb:第一章 - JDBC

JavaWeb入门1.0 1. javaweb介绍2. 数据库设计2.1 约束2.2 表关系2.3 多表查询2.3.1 内连接&#xff08;连接查询&#xff09;2.3.2 外连接&#xff08;连接查询&#xff09;2.3.3 子查询 2.4 事务 3. JDBC3.1 JDBC 快速入门 4 JDBC API详解4.1 DriverManager4.2 Conncetion4.3 …

13_Linux无设备树Platform设备驱动

目录 Linux驱动的分离与分层 驱动的分隔与分离 驱动的分层 platform平台驱动模型简介 platform总线 platform驱动 platform设备 platform设备程序编写 platform驱动程序编写 测试APP编写 运行测试 Linux驱动的分离与分层 像I2C、SPI、LCD 等这些复杂外设的驱动就不…

吴恩达ML2022-用于手写数字识别的神经网络

1 用到的包 导入在这个分配过程中需要的所有包。 Numpy 是使用 Python 进行科学计算的基本软件包。Matplotlib 是在 Python 中绘制图形的流行库。tensorflow是一种流行的机器学习平台。 import numpy as np import tensorflow as tf from tensorflow.keras.models import Se…

Java对象导论

对象具有状态、行为和标识。每个对象都可以拥有内部数据&#xff08;它们给出了该对象的状态&#xff09;和方法&#xff08;它们产生的行为&#xff09;&#xff0c;并且每个对象在内存中都有一个唯一的地址&#xff08;标识&#xff09;。 抽象过程就是在问题空间元素和解空…

Macbook下提升开发效率的几个小工具

最近倒腾mac笔记本&#xff0c;记录下一些高效率的工具吧。 首先就是alfred&#xff0c;内置可以自定义各种快捷命令查找&#xff0c;配合Dash来快速查找C系统API&#xff0c;其实Dash中包含了各种编程所需API文档&#xff0c;值得下载。 以前我都是直接查看cppreference.c…

【分享】Redis的五种基本数据类型和应用场景

前言&#xff1a; Redis支持五种基本数据类型&#xff1a; String&#xff08;字符串类型&#xff09;&#xff1a;可以是普通字符串&#xff0c;也可以是整数或浮点数值。可以设置过期时间&#xff1b;可以对字符串进行append、get、set、incr、decr等操作。Hash&#xff08…

【C++】位图和布隆过滤器

文章目录 位图概念难点代码 布隆过滤器概念插入查找删除优缺点代码 位图 概念 所谓位图&#xff0c;就是用每一个比特位位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常是用来判断某个数据存不存在的。 给40亿个不重复的无符号整数&#xff…

buu_Misc总结2

目录 百里挑一 exiftool: [SUCTF2018]followme grep工具使用&#xff1a; [安洵杯 2019]Attack mimikatz工具使用&#xff1a; 百里挑一 打开文件是流量包&#xff0c;发现里面有很多图片 导出http 另存一个文件夹&#xff0c;里面很多图片&#xff0c;啥也看不出来 &…

医用影像技术

1.X光和CT原理 X光和CT&#xff08;计算机断层扫描&#xff09;都是医学成像技术&#xff0c;用于诊断和治疗。它们的原理如下&#xff1a; X光原理&#xff1a; X光是一种电磁辐射&#xff0c;与可见光类似&#xff0c;但具有更高的能量。当X光通过人体或物体时&#xff0c;…

创作一周年纪念日【道阻且长,行则将至】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; 技术之外的往事 &#x1f383;所处时段&#xff1a; 大学生涯[1/2] 文章目录 一、起点一切皆有定数 二、成果尽心、尽力 三、相遇孤举者难起&#xff0c;众行者易趋 四、未来长风破浪会有时&#xff0c;直挂云…

[MySQL]MySQL表中数据的增删查改(CRUD)

[MySQL]MySQL表中数据的增删查改(CRUD) 文章目录 [MySQL]MySQL表中数据的增删查改(CRUD)1. 新增数据1.1 单列插入1.2 多列插入1.3 插入否则更新1.4 替换 2. 基本查询数据2.1 全列查询2.2 指定列查询2.3 查询字段为表达式2.4 为查询结果指定别名2.5 结果去重2.6 where子句2.7 or…

修复漏洞(二)离线升级Tomcat版本

前言 生产环境无法联网&#xff0c;只能通过下载离线版本更新Tomcat到小版本最新注意Tomcat10和11与jdk1.8都不兼容&#xff0c;只能更新到小版本的最新前提是按照我这种方法配置Tomcat开机自启的https://blog.csdn.net/qq_44648936/article/details/130022136 步骤 备份整个…

IAR编译报错:Error[Pe065: expected a “.“ and Error[Pe007]:unrecognized token

IAR报错 Error[Pe065: expected a “.” and Error[Pe007]:unrecognized token 使用IAR编译报如下错误&#xff1a; 找到软件报错的地方&#xff0c;从肉眼看&#xff0c;并没有错误的地方&#xff0c;如下图所示&#xff1a; 这时肯定是丈二和尚摸不着头脑&#xff0c;这里…