【Linux】进程程序替换 + 模拟实现简易shell

news2024/11/15 1:33:01

前言

上一节我们介绍了 **进程终止**和 **进程等待**等一系列问题,并做了相应的验证,本章将继续对进程控制进行介绍,重点学习进程程序替换,并进行相应验证,在此基础上,自己模拟实现一个shell,该shell能够实现执行命令操作。。

目录

    • 1.进程程序替换
      • 1.1为什么要做进程程序替换?
      • 1.2 进程程序替换的原理
      • 1.3 六个exec替换函数
        • 1.3.1 execl函数:
        • 1.3.2 execv 函数:
        • 1.3.3 execlp函数:
        • 1.3.4 execvp函数:
        • 1.3.5 execle函数:
        • 1.3.6 execve函数:
      • 1.4 实现简易版shell
        • 1.4.1 Shell 内建命令等问题的解决

1.进程程序替换

概念引入:

将可执行程序加载到内存,并且重新调整 子进程的页表映射,使之指向新的进程的代码和数据段,这种过程就叫做程序替换

  • 用fork创建子进程后执行的是和父进程相同的程序,因为代码共享
  • 但,如果我们想让创建出来的子进程,执行全新的程序呢,此时就需要用到进程的程序替换
  • 进程程序替换就相当于一个加载器的角色

1.1为什么要做进程程序替换?

原因:

  • 原因是我们想让我们的子进程执行一个全新的程序。
  • 不同语言写的功能(比如python shell) 互相调用,这就是为什么要有程序替换的原因。

我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两件种类事情

  • 1.让子进程执行父进程的代码片段(代码共享)
  • 2.让子进程执行磁盘中一个全新的程序(使用shell脚本, 想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等等)

1.2 进程程序替换的原理

程序替换的原理:

  • 将磁盘中的待执行的程序,加载入内存结构
  • 重新建立页表映射,谁执行程序替换,就重新建立子进程的映射关系

效果:让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序!

![

](https://i-blog.csdnimg.cn/direct/f4c725a0e19742b0831dec30e48d9525.png)

在这里插入图片描述
调整子进程的页表,让其不再与父进程代码和数据有任何关系,而是指向自己的代码和自己的数据区

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

总结:

  • 说白了就是让fork创建子进程,不想让子进程执行父进程代码片段。
  • 我们想让子进程执行磁盘当中全新的程序,而且我们没有创建新的进程。
  • 因为子进程的内核数据结构基本没变,只是重新建立了虚拟到物理的映射关系罢了

1.3 六个exec替换函数

程序替换的是子进程:(重点)
进程替换永远影响的是进程的本身,子进程的替换永远不会影响父进程,因为进程具有独立性
重新建立的是页表映射但并不影响内核数据结构的具体情况.

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

#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[]);
int execve(const char *path, char *const argv[], char *const envp[]);

函数解释

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

函数命名理解

  • (list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
    在这里插入图片描述
1.3.1 execl函数:
int execl(const char *path, const char *arg, ...);
path:这个是路径,可执行程序的路径
arg: 可变参数,可以传多个参数,参数要以列表形式写,比如(1s -1 -a) ,就要写成"ls",“-l”,“-a”,NULL, 
注意:最后必须是NULL结尾,表示参数传递完毕。
 

代码演示:

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

int main()
{
    //让我的程序执行系统上的: ls -a -i这样的一个命令
    printf("我是一个进程,我的pid是 : %d\n", getpid());

    //int ret = execl("/usr/bin/ls", "ls", "-l", "-a", NULL); //带选项
 
    //替换失败的情况
    int ret = execl("/usr/bin/lsssss", "ls", "-l", "-a", NULL); //带选项
    printf("我执行完毕了,我的pid : %d, ret = %d\n", getpid(), ret);

    return 0;
}
  • 一旦替换成功,是将当前进程的代码和数据全部替换了!!
  • 前一个printf被执行是因为程序替换并没有执行。
1.3.2 execv 函数:
int execv(const char *path, char *const argv[]); %%
实现的功能和execl一模一样。
path:这个是路径,可执行程序的路径
argv[]: 和execl的唯一区别就是传参方式的不一样,这个要传入数组har* const argv_[] = {"ls","-l","-a","-i", NULL};
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        //我们要子进程执行全新的程序,以前我们是子进程执行父进程的代码片段
        
        printf("我是子进程,我的pid是:%d\n", getpid());
        //char* const argv_[] = {
        //    (char*)"ls",
        //    (char*)"-l",
        //    (char*)"-a",
        //    (char*)"-i",
        //    NULL
        //};
        
        char* const argv_[] = {
            (char*)"top",
            NULL 
        };
       //execv("/usr/bin/ls", argv_);

       execv("/usr/bin/top", argv_);   
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if(ret == id)
    {
        sleep(2);
        printf("进程等待成功!\n");
    }

    return 0;
}
1.3.3 execlp函数:
int execlp(const char *file, const char *arg, ...);
file: 你想执行程序的名字,注意:命名中带P的,可以不用带可执行程序的路径。
arg:以列表形式传参

代码演示:

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

//有时候不想让父进程做一件事,只想让子进程做一件事
//将进程创建引入进来

int main()
{
    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    {
        printf("我是子进程,我的pid是:%d\n", getpid());
        execlp("ls", "ls", "-a", "-l", "-i", NULL);//这里出现了两个ls,含义一样吗?-- 不一样!
        //第一个参数是供系统去找要执行谁的指令,后面一坨是表示如何执行该指令
        
        exit(100); //只要执行了exit,就意味着,execl系列的函数失败了 -- 进程替换失败了
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);%0代表阻塞等待
    if(ret == id)
    {
        sleep(2);
        printf("wait success, ret : %d, 我所等待子进程的退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
    }

    return 0;
}
1.3.4 execvp函数:

作用和execIp是一样的,只不过传参形式不一样。。

int execvp(const char *file, char *const argv[]);
file: 你想执行程序的名字,注意:命名中带P的,可以不用带可执行程序的路径。
arg:以数组形式传参

代码演示:

    char* const argv_[] = {
        (char*)"top",
        NULL 
    };
   execvp("ls", argv_ );
1.3.5 execle函数:

多了一个参数,是环境变量。

int execle(const char *path, const char *arg, ..., char * const envp[]);
path:这个是路径,可执行程序的路径
arg: 可变参数,可以传多个参数,参数要以列表形式写,比如(1s -1 -a) ,就要写成"ls",“-l”,“-a”,NULL, 
注意:最后必须是NULL结尾,表示参数传递完毕。
 envp:环境变量

代码演示:

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

int main()
{
    //环境变量的指针声明
    extern char** environ;

    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    { 
        printf("我是子进程,我的pid是:%d\n", getpid());
        
        //我们来手动导入一个环境变量
        char* const env_[] = {
            (char*)"MYPATH=You Can See Me!!",
            NULL 
        };

        //e: 添加环境变量给目标进程,是覆盖式的!
        //execle("./mycmd", "mycmd", NULL, env_);
        
        //execle("/usr/bin/ls", "ls", NULL, env_);

        execle("./mycmd", "mycmd", NULL, environ);

        exit(100); //只要执行了exit,就意味着,execl系列的函数失败了 -- 进程替换失败了
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if(ret == id)
    {
        sleep(2);
        printf("进程等待成功!\n");
    }

    return 0;
}

使用execle()添加环境变量给目标进程,是覆盖式的!会把原来的environ环境变量都改掉
所以环境变量只剩下MYPATHT

正确做法是:将全部环境变量传过去,将environ传过去。

在这里插入图片描述

补充重点1:

  • 子进程会继承父进程的环境变量的,当父进程调用fork()创建子进程时,子进程会继承父进程的所有环境变量。
  • 当子进程调用execlp()等函数执行其他程序时,子进程也会继承父进程的环境变量.
  • 如果需要在子进程中更改环境变量,可以使用setenv()或putenv()等函数进行更改.
  • 更改的环境变量只会影响当前进程和它的子进程,并不会影响父进程或其他进程的环境变量

补充重点2:
ls 是一个常见的系统命令, 它通常位于系统的某个标准路径(如 /bin 或 /usr/bin),即使 PATH 为空,execlp() 会检查这些标准路径,找到 ls 的可执行文件并执行它。

  1. 如果PATH环境变量为空,execlp()函数会无法在环境变量中查找可执行文件的路径。但是,execle()函数会检查一些默认路径,例如/bin、/usr/bin等,来查找可执行文件。因此,即使PATH为空execle()函数也可能会在这些默认路径中找到可执行文件并执行它

  2. 但是,如果在默认路径中也找不到可执行文件,则execle()函数会执行失败,并将errno设置为ENOENT,表示无法找到可执行文件。因此,如果需要执行特定路径下的可执行文件,最好使用execv()或execve()等函数,并指定可执行文件的完整路径。这样可以避免依赖PATH环境变量来查找可执行文件的路径。

1.3.6 execve函数:
int execve(const char *path, char *const argv[], char *const envp[]);

有了上面的execle()函数的讲解,理解就不复杂了,只是第二个参数传的不同,这里传的是一个指针数组。
在这里插入图片描述

为什么有那么多的接口?

  • 目的是:适配应用场景。
  • 其实上述函数都是对系统调用接口的封装。

1.4 实现简易版shell

只要我们懂得了程序替换的原理,会用程序替换的接口,就很好理解:

  • shell本身执行起来就是个死循环
  • shell创建子进程,将子进程给替换掉就ok了

要写一个shell,需要循环以下过程

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父等待子进程退出(wait)
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define SEP " "
#define MAX_CMD 1024
#define SIZE 128

char command_line[MAX_CMD];
char* command_args[SIZE];

char env_buffer[NUM];

extern char** environ;

//对应上层的内建命令
int ChangeDir(const char* new_path)
{
    chdir(new_path);

    return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
    putenv(new_env);
}

int main()
{
    //shell本质就是一个死循环
    while(1)
    {
        //不关心获取这些属性的接口,搜索一下都有
        
        //1.显示提示符
        printf("[用户名@我的主机名 当前目录]# ");
        fflush(stdout);

        //2.获取用户输入
        memset(command_line, '\0', sizeof(command_line)); //初始化

        //从键盘获取,标准输入,stdin,获取到的是C风格的字符串(stdio.h结尾的),'\0'结尾
        fgets(command_line, NUM, stdin);
        command_line[strlen(command_line) - 1] = '\0';//清空\n回车
        //printf("%s\n", command_line);

        //3. "ls -a -l -i" -> "ls" "-a" "-l" "-i" 字符串切分 -- 因为这些参数一定得以列表或者数组方式传递给程序替换接口
        //shell必须切分,因为必须调用execl函数
        
        //将第一个字符串地址用0号下标指向,第二个字符串地址用1号下标指向 
        command_args[0] = strtok(command_line, SEP);

        int index = 1;

        //给ls命令添加颜色: 如果提取出来的程序名是ls -- 1下标设置成改颜色的
        if(strcmp(command_args[0], "ls") == 0) command_args[index++] = (char*)"--color=auto";

        //strtok截取成功返回字符串起始地址
        //截取失败,返回NULL
        while(command_args[index++] = strtok(NULL, SEP));
        
        //for debug
        //int i = 0;
        //for(i = 0; i < index; i++)
        //{
        //    printf("%d : %s\n", i, command_args[i]);
        //}
        
        //4.TODO -- 编写后面的逻辑,内建命令(由父Shell自己实现的自己调用的一个函数)
        if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
        {
            //让调用方进行路径切换,父进程
            ChangeDir(command_args[1]);
            continue;
        }
        
        //走到这里一定是将命令行参数解析完了,包括命令 + 选项
        
        //将环境变量的信息导入在了父进程的上下文当中
        if(strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
        {
            //环境变量列表(是个指针数组,每个元素是个指针指向一个环境变量)
            //我们传的是一个字符串首地址,但是环境变量的内容还是我们自己维护的
            //目前,环境变量信息在comman_line,会被清空,那么环境变量当然就没有了
            //所以此处我们需要自己保存一下环境变量的内容
            
            strcpy(env_buffer, command_args[1]);
            PutEnvInMyShell(env_buffer);
            //PutEnvInMyShell(command_args[1]);//MYENV=112233
            continue;
        }

        //5.创建进程Fork,执行
        //如果自己直接程序替换的话,就把自己写的shell给替换了
        
        pid_t id = fork();
        if(id == 0)
        {
            //子进程
            //6.程序替换
           
            //execvpe(command_args[0], command_args, environ);
            execvp(command_args[0], command_args);

            exit(1);//执行到这里,子进程一定替换失败了
        }

        int status = 0;
        
        pid_t ret = waitpid(id, &status, 0);

        if(ret > 0)
        {
            printf("等待子进程成功: sig: %d, code: %d\n", status & 0x007F, (status & 0xFF00) >> 8);
        }

    }//end while

    return 0;
}
1.4.1 Shell 内建命令等问题的解决
  • cd命令的处理
    在命令行中使用cd指令,会跳转路径,如果使用绝对命令,就不行了

  • 一个进程也存在对应路径,进程对应的路径可以理解成:当进程启动的时候,在那个路径启动,这个进程所在的路径就是当前进程所启动的路径。

  • 如果我们不对cd进行特殊处理,则子进程路径切换后,并不影响父进程的路径,会发现命令路径还是没变化。

所以,对应这个命令,就不能用程序替换的方式,来执行一些特殊的命令了。
而是,在父进程中将一些命令,单独处理。

重点:

程序替换影响的是子进程和父进程没关系,子进程一 跑就完了,曾经所有的操作就没有意义,路径切换就没意义了,所以我们要让父进程的路径发生变化。
如果有些行为,是必须让父进程shell执行的,不想让子进程执行,绝对不能创建子进程!只能是父进程自己实现对应的代码!

正确做法:
使用系统中,更改工作目录的函数。
在这里插入图片描述

 if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
        {
            //让调用方进行路径切换,父进程
            ChangeDir(command_args[1]);
            continue;
        }
  • 内建命令:
    由shell自己执行的命令,我们称之为内建(内置bind- in)命令。

  • export的处理:
    导入环境变量:

  • export不是一个可执行程序和cd,ls,cat等指令不同。

  • export是一个shell内置命令,用于设置环境变量。它并不是一个可执行程序,而是由shell解释器直接执行的命令

所以,在使用execvp进行程序替换的时候,是不能替换成功的!
在这里插入图片描述
注意:

  • 环境变量是属于系统的数据,子进程在执行程序替换时,当前进程的环境变量数据,不会被替换掉,而且是以父进程为模版继承下来的
  • 所以才会让父进程以内建命令的方式putenv,子进程就能直接获取了.
  • 环境变量会被子进程继承下去,所以他会有全局属性。
  • 必须,Export放在父进程的内建命令中实现,因为放在子进程中,父进程内的环境变量,就没有改变,是有问题的。。

尾声
看到这里,相信大家对这个Linux有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

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

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

相关文章

前端web性能统计

前端web性能统计 1. 背景2. 业界方案2.1 腾讯2.2 蚂蚁金服2.3 字节跳动2.4 美团 3. 相关观念3.1 RAIL模型3.2 性能指标3.3 真实用户监控3.4 performance 4. 性能监控工具介绍5. 推荐采用方案 1. 背景 在如今的数字时代&#xff0c;网站和应用程序的性能对用户体验至关重要。用…

机器人相关工科专业课程体系

机器人相关工科专业课程体系 前言传统工科专业机械工程自动化/控制工程计算机科学与技术 新兴工科专业智能制造人工智能机器人工程 总结Reference: 前言 机器人工程专业是一个多领域交叉的前沿学科&#xff0c;涉及自然科学、工程技术、社会科学、人文科学等相关学科的理论、方…

FOC(笔记二)

接上篇文章&#xff1a;FOC算法(笔记一)_马鞍波和三角波调制合成-CSDN博客 前面已经对FOC的开环控制进行了介绍&#xff0c;下面对FOC的闭环控制进行介绍。 本次使用的电机参数如下图所示&#xff1a; 一、HALL传感器 1.1、霍尔传感器的角度、速度计算 因为本次使用的是120安…

SpringCloud02_consul概述、功能及下载、服务注册与发现、配置与刷新

文章目录 ①. Euraka为什么被废弃②. consul简介、如何下载③. consul功能及下载④. 服务注册与发现 - 8001改造⑤. 服务注册与发现 - 80改造⑥. 服务配置与刷新Refresh ①. Euraka为什么被废弃 ①. Eureka停更进维 ②. Eureka对初学者不友好,下图为自我保护机制 ③. 阿里巴巴…

taro小程序terser-webpack-plugin插件不生效(vue2版本)

背景 最近在做公司内部的小程序脚手架&#xff0c;为了兼容老项目和旧项目&#xff0c;做了vue2taro,vue3taro两个模板&#xff0c;发现terser-webpack-plugin在vue2和vue3中的使用方式并不相同&#xff0c;同样的配置在vue3webpack5中生效&#xff0c;但是在vue2webpack4中就…

【Linux】:重定向和缓冲区

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来关于重定向和缓冲区的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精…

【题解】 栈和排序(栈 + 预处理 / 贪心)

https://www.nowcoder.com/practice/95cb356556cf430f912e7bdf1bc2ec8f?tpId196&tqId37173&ru/exam/oj 预处理最大值 #include <climits> // 包含标准整数类型的定义 #include <vector> // 包含标准vector容器的定义class Solution {public:/*** 栈排…

接着探索Linux的世界 -- 基本指令(文件查看、时间相关、打包压缩等等)

话不多说&#xff0c;直接进入主题 一、cat指令 -- 查看目标文件的内容 语法&#xff1a;cat [选项][文件] 功能&#xff1a; 查看目标文件的内容 -b 对非空输出行编号 -n 对输出的所有行编号 -s 不输出多行空行 1、查看目标文件的内容 2、 -b 对非空输出行编号 3、-n 对…

论文浅尝 | 学会使用上下文学习来进行命名实体识别

笔记整理&#xff1a;王润哲&#xff0c;东南大学硕士&#xff0c;研究方向为大模型 链接&#xff1a;https://aclanthology.org/2023.acl-long.764.pdf 1. 动机 实体关系是知识图谱中不可或缺的一层重要信息&#xff0c;它们描述了实体之间的语义关系&#xff0c;这种连接使得…

【填坑指南】PHP8报:Unable to load dynamic library ‘zip.so’ 错误

1.原因分析 这种情况多数发生在PHP安装时因为各种原因失败后&#xff0c;残余的库与最后安装的PHP版本不兼容导致的。 2.我的路径 一开始我按照以前摸索出来的安装PHP7.3的成功经验来编译方法安装PHP8.3&#xff0c;发现以前的套路已经失效了。反复重装PHP8.3失败后&#xf…

Visual Studio 安装程序无法执行修复或更新

一.问题场景 出现问题的场景&#xff1a;当你的VS已经安装但是无法在工具中下载新组件或者卸载了当时一直无法安装。 二.问题原因 如果计算机上的 Visual Studio 实例已损坏&#xff0c;则可能会出现此问题。 三.解决方法 如果之前尝试修复或更新 Visual Studio 失败&…

Qt5离线安装包无法下载问题解决办法

Qt5离线安装包无法下载问题解决办法 文章目录 Qt5离线安装包无法下载问题解决办法1、前言2、Qt5安装包下载办法 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;Qt开发经验 &#x1f448; 1、前言 Qt安装包官方下载地址 Qt5离线安装包目前在国内已经被墙…

Meta即将推出4000亿的Llama 3 超级AI模型,或将改写大语言模型竞争格局!|TodayAI

2024年4月&#xff0c;科技巨头Meta发布了其最新的AI大型语言模型——Llama 3&#xff0c;该模型基于一个至少比前代产品Llama 2大七倍的数据集&#xff0c;展现出前所未有的性能。在最初发布时&#xff0c;Llama 3提供了8B和70B两种参数规模的版本&#xff0c;并迅速超越了Goo…

SpringBoot新手快速入门系列教程十一:自动生成API文档,Springboot3.x集成SpringDoc

本次项目我们用Maven来做&#xff0c;最近发现gradle其实很多项目的支持比较差&#xff0c;所以项目还是用Maven来新建项目。对比了市面上的几种API生成第三方库&#xff0c;只有springdoc 是能够按照文档就能部署出来的。 官网&#xff1a; OpenAPI 3 Library for spring-bo…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(八)-通过无人机进行无线接入

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

C++基础(二十):常见C++11的新特性

1979年&#xff0c;贝尔实验室的本贾尼等人试图分析unix内核的时候&#xff0c;试图将内核模块化&#xff0c;于是在C 语言的基础上进行扩展&#xff0c;增加了类的机制&#xff0c;完成了一个可以运行的预处理程序&#xff0c;称之为C with classes。语言的发展就像是练功打怪…

gd32发送数据,定义参数,接收中断

void usart_receive_data(uint8_t ucch) {usart_data_receive(UART3); } void usart_send_data(uint8_t ucch) {usart_data_transmit(UART3,(uint8_t)ucch);while(usart_flag_get(UART3,USART_FLAG_TBE) RESET); } 这是在c文件中定义函数&#xff0c;之后在h文件中声明&#…

记录些Redis题集(2)

Redis 的多路IO复用 多路I/O复用是一种同时监听多个文件描述符&#xff08;如Socket&#xff09;的状态变化&#xff0c;并能在某个文件描述符就绪时执行相应操作的技术。在Redis中&#xff0c;多路I/O复用技术主要用于处理客户端的连接请求和读写操作&#xff0c;以实现高并发…

eProsima Fast DDS getting started

系列文章目录 文章目录 系列文章目录preface**对象与数据结构**● Publish-Subscriber模块● RTPS模块**配置Attributes** Discovery传输控制前言0、安装cmake安装相关源码安装&#xff1a;- A foonathan_memory_vendor- C fastcdr- D tinyxml2- E asio- F openssl- G fastrtps…

贪心:交换论证法

目录 切蛋糕的最小总开销 切蛋糕的最小总开销 交换论证&#xff1a; 设横切的开销为 h&#xff0c;如果先横切&#xff0c;设需要横切 cnt_h 次。 设竖切的开销为 v&#xff0c;如果先竖切&#xff0c;设需要竖切 cnt_v 次。 先横切&#xff0c;再竖切&#xff0c;那么竖切…