(17)Linux的进程阻塞进程程序替换 exec 函数簇

前言:本章我们讲解它的 options 参数。在讲解之前我们需要理解进程阻塞,然后我们重点讲解二进程程序替换,这是本章的重点,然后介绍一个进程替换函数 execl,通过介绍这个函数来打开突破口,引入进程创建的知识点。最后,我们在学习进程创建的 exec 函数簇。

一、进程阻塞(Process Blocking)

1、继续讲解 waitpid

 我们先来简单回顾一下上一章的内容: 

#include <sys/types.h>
#include <sys/wait.h>
 
pid_t waitpid(pid_t pid, int* status, int options);

 上一章介绍了 status 参数,知道了如何通过位操作来截 status 获取进程错误码与错误信号:

status&0x7F        // 获取错误信号
(status>>8)&0xFF   // 获取错误码

但是我们还是不建议这么做,因为直接用操作系统提供的宏就好了,我们可以通过 WIFEXITED 宏来检测子进程是否正常退出(检测进程退出时信号是否为 0),在用 WEXITSTAUS 宏还获取进程的退出码:

if (WIFEEXITED(status)) 
{
    printf("等待成功: exit code: %d\n", WIFEEXITED(status));
}

这些都是上一章讲解 status 参数的内容了,我们下面要讲的是 waitpid 另一个参数 options。 

options 为 0,则标识为 阻塞等待 

比如:如果子进程不退出,父进程在等,等的时候子进程是卡在那等的。

2、理解进程阻塞

思考:如何理解父进程进程阻塞? 

首先,进程状态我们说过:如果一个进程在系统层面上要等待某件事情发生,

但这件事情还没发生,那么当前进程的代码还没法向后运行,只能让该进程处于阻塞状态。

就是让父进程的 task_struct 状态由 R\rightarrow S,从运行队列投入到等待队列,等待子进程退出。

子进程退出的本质是条件就绪,如果子进程退出条件一旦就绪,操作系统会逆向地做上述工作。

将父进程的\textrm{pcb}从等待队列再搬回运行队列,并将状态S\rightarrow R,此时父进程就会继续运行。

 3、轮询检测(Polling)

所谓的阻塞,其实就是挂起。在上层表现来看,就是进程卡住了 

而非阻塞式等待是会做些自己的事,而不是傻等!

多次调用非阻塞接口,这个过程我们称之为 轮询检测 (Polling)。

我们上一章中讲解 waitpid 时,举的例子都是 阻塞式 的等待。

如果我们想 非阻塞式 的等,我们可以设置 options 选项为 WNOHANG (With No Hang)。

这个选项通过字面很好理解,就是等待的时候不要给我挂 (Hang) 住,其实就是非阻塞!

现在我们正式介绍一下 waitpid 的返回值:

  • 如果此时等待成功,返回值是子进程的退出码。
  • 如果你是非阻塞等待 (WNOHANG),等待的子进程没有退出,返回值为 0。

4、 基于非阻塞的轮询等待(waitpid)

 如果我们想把我们上一章节,演示 waitpid 使用方式的代码,改为非阻塞等待。

我们只需要将 waitpid 的 options 参数加上。

代码演示:基于非阻塞的轮询等待

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main(void)
{
    pid_t id = fork();
    if (id == 0) {
        // 子进程
        while (1) {
            printf("子进程:我的PID: %d,我的PPID: %d\n", getpid(), getppid());
            sleep(5);  // 先睡眠 5s,5s后退出
            break;
        }
        exit(233);
    }
    else if (id > 0) {
        // 父进程
 
        /* 基于非阻塞的轮询等待方案 */
        int status = 0;
        while (1) {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if (ret > 0) {          // 等待成功
                printf("等待成功,%d,退出信号: %d,退出码: %d\n", ret, status&0x7F, (status>>8)&0xFF);
            }
            else if (ret == 0) {    // 等待成功,但是子进程没有退出
                printf("父进程:子进程还没好,那我先做其他事情\n");
                sleep(1);
            }
            else {
                // 出错了,暂时不作处理
            }
        }
    }
    else {
        // 什么也不做
    }
}

说明:我们只需要将 waitpid 中的 options 参数带上 WHOHANG 就可以了。返回值 ret>0 就是等待成功,我们这里新增一个等于 0 的判断,作为 "等待成功但是子进程还没有退出" 的情况,因为等待的子进程没有退出,返回值为 0 。运行后,就会问子进程好没好,如果没有好父进程就可以做自己的事情了,这就是非阻塞式轮询等待。
运行结果如下:

我们可以让父进程在非阻塞等待时真正做点事 

代码演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <vector>
 
typedef void (* handler_t)();  // 函数指针类型
 
// 方法集
std::vector<handler_t> handlers;
 
void func1() {
    printf("Hello,我是方法1\n");
}
void func2() {
    printf("Hello,我是方法2\n");
}
 
void Load() {
    // 加载方法
    handlers.push_back(func1);
    handlers.push_back(func2);
}
 
int main(void)
{
    pid_t id = fork();
    if (id == 0) {
        // 子进程
        while (1) {
            printf("子进程:我的PID: %d,我的PPID: %d\n", getpid(), getppid());
            sleep(3);
        }
        exit(233);
    }
    else if (id > 0) {
        // 父进程
 
        /* 基于非阻塞的轮训等待方案 */
        int status = 0;
        while (1) {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if (ret > 0) {          // 等待成功
                printf("等待成功,%d,退出信号: %d,退出码: %d\n", ret, status&0x7F, (status>>8)&0xFF);
            }
            else if (ret == 0) {    // 等待成功,但是子进程没有退出
                printf("父进程:子进程好了没?哦,还没,那我先做其他事情啦\n");
                if (handlers.empty()) {
                    Load();
                }
                for (auto f : handlers) {
                    f();  // 回调处理对应的任务
                }
 
                sleep(1);
            }
            else {
                // 出错了,暂时不作处理
            }
        }
    }
    else {
        // 什么也不做
    }
}

如果你想要你的程序直接父进程做更多的事情,把方法加到 Load 里就可以了。

写下 Makefile:

mytest:mytest.cpp
    g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
 rm -f mytest

运行结果如下:

二、 进程程序替换(Process Substitution)

1、 让子进程执行一个新的程序

我们之前做的所有代码演示,子进程执行的都是父进程的代码片段。

如果我们想让创建出来的子进程,执行全新的程序呢? 

之前我们通过写时拷贝,让子进程和父进程在数据上互相解耦,保证独立性。如果想让子进程和父进程彻底分开,让子进程彻彻底底地执行一个全新的程序,我们就需要 进程的程序替换

那为什么要让子进程执行新的程序呢?

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

  •     让子进程执行父进程的代码片段(服务器代码…)
  •     想让子进程执行磁盘中一个全新的程序(shell、想让客户端执行对应的程序、通过我们的进程执行其他人写的进程代码、C/C++ 程序调用别人写的 C/C++/Python/Shell/Php/Java...)

2、程序替换原理 

程序替换的原理:

  • 将磁盘中的内存,加载入内存结构。
  • 重新建立页表映射,设执行程序替换,就重新建立谁的映射(下图为子进程建立)。
  • 效果:让父进程和子进程彻底分离,并让子进程执行一个全新的程序!

程序替换的本质

本质上就是去替换一个进程pcb在内存中的对应的代码和数据(加载新程序到内存——>更新页表信息——>初始化虚拟地址空间),然后这个进程pcb重新开始执行这个新的程序

这个过程有没有创建新的进程呢?没有!根本就没有创建新的进程!

因为子进程的内核数据结构根本没变,只是重新建立了虚拟的物理地址之间的映射关系罢了。

 内核数据结构没有发生任何变化! 包括子进程的 \textrm{pid}\textrm{pid} 都不变,说明压根没有创建新进程。

 以可变参数列表的接收参数的 execl 接口

我们要调用接口,让操作系统去完成这个工作 —— 系统调用。

如何进行程序替换?我们先见见猪跑 —— 从 execl 这个接口讲,看看它怎么跑的。

int execl(const char* path, const char& arg, ...);

如果我们想执行一个全新的程序,我们需要做几件事情:

  • 第一件事情:先找到这个程序在哪里。
  • 第二件事情:程序可能携带选项进行执行(也可以不携带)。

 简单来说就是:① 程序在哪?  ② 怎么执行?

所以,execl 这个接口就必须得把这两个功能都体现出来!

  •     它的第一个参数是 path,属于路径。
  •     参数  const char* arg, ... 中的 ... 表示可变参数,命令行怎么写(ls, -l, -a) 这个参数就怎么填。ls, -l, -a 最后必须以 NULL 结尾,表示 "如何执行程序的" 参数传递完毕。

代码演示:exec() 

#include <stdio.h>
#include <unistd.h>
 
int main(void)
{
    printf("我是一个进程,我的PID是:%d\n", getpid());
    // ls -a -l
    execl("/usr/bin/ls", "ls", "-l", "-a", NULL);  // 带选项
 
    printf("我执行完毕了,我的PID是:%d\n", getpid());
 
    return 0;
}

运行结果:

 运行最后就会执行ls命令execl("/usr/bin/ls","ls","-l","-a",NULL);

刚才是带选项的,现在我们再来演示一下不带选项的: 

 execl("/usr/bin/top", "top", NULL);  // 不带选项

运行结果如下:

这样我们的程序就直接能执行 top 命令了,除此之外,我们曾经学的大部分命令其实都可以通过 execl 执行起来。这就叫做 程序替换

printf("我执行完毕了,我的PID是:%d\n", getpid());

这句话为什么没有打印出来??

为什么我们最后的代码并没有被打印出来?

因为 一旦替换成功,是会将当前进程的代码和数据全部替换的!

所以自然后面的 printf 代码早就被替换了,这意味着该代码不复存在了,荡然无存!

因为在程序替换的时候,就已经把对应进程的代码和数据替换掉了!

而第一个 printf 执行了的原因自然是因为程序还没有执行替换,
所以,这里的程序替换函数用不用判断返回值?为什么?

int ret = execl(...);

一旦替换成功,还会执行返回语句吗?返回值有意义吗? 没有意义的!

 程序替换不用判断返回值!因为只要成功了,就不会有返回值。 而失败的时候,必然会继续向后执行。通过返回值最多能得到是什么原因导致替换失败的。只要执行了后面的代码,看都不用看,一定是替换失败了;只要有返回值,就一定是替换失败了。

我们来模拟一下失败的情况,我们来执行一个不存在的指令 :

execl("/usr/bin/lssssss", "ls", "-l", "-a", NULL);  // 带选项

execl 替换失败,就会继续向后执行。但是,一旦 execl 成功后就会跟着新程序的逻辑走,就不会再 return 了,再也不回来了,所以返回值加不加无所谓了。

3、引入进程创建 

以前我们的示例都是让子进程执行父进程的代码,我们今天想让子进程执行自己的程序。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
 
int main(void) 
{
    printf("我是父进程,我的PID是: %d\n", getpid());
    pid_t id = fork();
    if (id == 0) {
        /* child 
           我们想让子进程执行全新的程序 */
        printf("我是子进程,我的PID是:%d\n", getpid());
        execl("/usr/bin/ls", "ls", "-a", "-l", NULL);  /* 让子进程执行替换 */
 
        exit(1);   /* 只要执行了exit,就意味着 excel 系列函数失败了,终止子进程*/
    }
 
    /* 一定是父进程 */
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if (ret == id) {
        /* 等待成功 */
        sleep(2);  
        printf("父进程等待成功!\n");
    }
 
    return( 0);
}

 运行结果:

成功执行代码,父进程也等待成功了。这里的子进程没有执行父进程的代码,而是执行了自己的程序。

子进程执行程序替换,会不会影响父进程呢?不会!因为进程具有独立性。

当程序替换的时候,我们可以理解成 —— 代码和数据都发生了写时拷贝,完成了父子分离。

三、exec 函数簇(Sheaf of functions exec)

1、以指针数组接收参数的 execv 接口

刚才我们学会了 execl 接口,我们下面开始学习更多的 exec 接口!它们都是用来替换的。

下面我们先来讲解一下和 execl 很近似的 execv:

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

path 参数和 execl 一样,关注的都是 "如何找到"

argv[] 参数关注的是 "如何执行",是个指针数组,放 char* 类型,指向一个个字符串。

大家在命令行上 $ ls -a -l ,在 execl 里我们是这么传的: "ls", "-a", "-l", NULL 。

所以 execv 和 execl 只有传参方式的区别,一个是可变参数列表 (l),一个是指针数组 (v)。

值得注意的是,在构建 argv[] 的时,结尾仍然是要加上 NULL!
代码:execv()

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
 #define NUM 16
    int main()
{
pid_t id=fork();
if(id==0){
    //子进程
    //ls -a -l
  printf("子进程开始运行,pid: %d\n",getpid());
    sleep(3);
    char* const _argv[NUM]={
      (char*)"ls",
      (char*)"-l",
      (char*)"-a",
      NULL
    };
   //execl("/usr/bin/ls","ls","-a","-l",NULL);
   execv("/usr/bin/ls",_argv);
    exit(1);
  
    }
    else{
    //父进程
      int status=0;
      printf("父进程开始运行,pid: %d\n",getpid());
      pid_t id=waitpid(-1,&status,0);//阻塞等待                                                                                                                      
      if(id>0){
        printf("wait success,exit code: %d\n",WEXITSTATUS(status));
      }             
    }               
                      
    return 0;
  }

运行结果:

2、无需带路径就能直接执行的 execlp 接口(可变参数列表)

int execlp(const char* file, const char* arg, ...);

execlp,它的作用和 execv、execl 是一样的,它的作用也是执行一个新的程序。

仍然是需要两步:① 找到这个程序   ② 告诉我怎么执行

第一个参数 file 也是 "你想执行什么程序",第二个参数 arg 是 "如何去执行它"。

所以这一块的参数传递,和 execl 是一样的,唯一的区别是比 execl 多了一个 p

我们执行指令的时候,默认的搜索路径在环境变量 \textrm{PATH} 中,所以这个 p 的意思是环境变量。

这意味着:执行 execlp 时,会直接在环境变量中找,不用去输路径了,只要程序名即可。

execlp("ls", "ls", "-a", "-l", "NULL");   // 路径都不用,直接扔

 这里出现的两个 ls 含义是不一样的,不可以省略

代码演示:execlp()

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
 #define NUM 16
    int main()
{
pid_t id=fork();
if(id==0){
    //子进程
    //ls -a -l
  printf("子进程开始运行,pid: %d\n",getpid());
    sleep(3);
    char* const _argv[NUM]={
      (char*)"ls",
      (char*)"-l",
      (char*)"-a",
      NULL
    };
   //execl("/usr/bin/ls","ls","-a","-l",NULL);
    //execv("/usr/bin/ls",_argv);
    execlp("ls","ls","-l","-a",NULL);
    exit(1);
  
    }
    else{
    //父进程
      int status=0;
      printf("父进程开始运行,pid: %d\n",getpid());
      pid_t id=waitpid(-1,&status,0);//阻塞等待                                                                                                                      
      if(id>0){
        printf("wait success,exit code: %d\n",WEXITSTATUS(status));
      }             
    }               
                      
    return 0;
  }

运行结果:

 3、无需带路径的 execvp 接口(指针数组)

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

execvp 也是带 p 的,执行 execvp 时,会直接在环境变量中找,只要程序名即可。

代码

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

运行结果:

 目前我们执行的程序,全部都是系统命令,如果我们要执行自己写的 C/C++ 程序呢?

4、利用 exec 调各种程序

假设有两个可执行程序:mycmd.c & exec.c,我们期望用 exec.c 调用 mycmp.c:

//mycmd.c
#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
   
int main(int argc,char *argv[])  
{  
  if(argc!=2){  
    printf("can not execute!\n");  
   exit(1);  
  }  
  if(strcmp(argv[1],"-a")==0){  
    printf("helllo a!\n");
  }
  else if(strcmp(argv[1],"-b")==0){
    printf("hello b!\n");
  }
  else{
    printf("default!\n");
  }

  return 0;                                                                                                                                                        
                                                                                         
}   

也就是 C 语言的可执行程序调用 C++ 的可执行程序,我们先来设计一下 Makefile。

我们需要在前面添加 .PHONY:all ,让伪目标 all 依赖 exec 和 mycmd。

如果不这样做,直接写,默认生成的是 mycmd,轮不到后面的 exec,属于 "先到先得"。

且 Makefile 默认也只能形成一个可执行程序,想要形成多个就需要用到 all 了。

 

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
 #define NUM 16
const char *myfile="/home/amx/lesson3/mycmd";
    int main()
{
pid_t id=fork();
if(id==0){
    //子进程
    //ls -a -l
  printf("子进程开始运行,pid: %d\n",getpid());
    sleep(3);
    char* const _argv[NUM]={
      (char*)"ls",
      (char*)"-l",
      (char*)"-a",
      NULL
    };
   execl(myfile,"mycmd","-a",NULL);
    //execv("/usr/bin/ls",_argv);
    //execlp("ls","ls","-l","-a",NULL);
    exit(1);
  
    }
    else{
    //父进程
      int status=0;
      printf("父进程开始运行,pid: %d\n",getpid());
      pid_t id=waitpid(-1,&status,0);//阻塞等待                                                                                                                      
      if(id>0){
        printf("wait success,exit code: %d\n",WEXITSTATUS(status));
      }             
    }               
                      
    return 0;
  }

 

 运行结果:

这是用的是绝对路径,那我们使用相对路径可以吗?

结果:

依然可以!!!!

那么如何 执行其它语言的程序呢??

我们创建两个文件

准备工作:先给两个文件写一些内容

试试能否运行:

都没问题:

那么我们修改我们的exec.c文件

运行结果:

完美!!!

运行test.sh也是一样!!!

运行结果:

还有一种方法:

修改:exec.c

一样可以!!

5、添加环境变量给目标进程的 execle 接口

 

int execle(const char* path, const char* arg, ..., char* const envp[]);

我们可以使用 execle 接口传递环境变量,相当于自己把环境变量导进去。

打开 mycmd 文件,我们加上几句环境变量:

 我们自己在exec.c中定义一个

传进去:

 我们来试一下:

 超级缝合怪 execvpe 接口

v - 数组,p - 文件名,e - 可自定义环境变量:

int execvpe(const char* file, char* const argv[], char* const envp[]);

这也没什么好说的,execle、execve、execvpe 都是 "环境变量" 一伙的。

6、为什么会有这么多 exec 接口?

唯一的差别就是传参的方式不一样,有的带路径,有的不带路径,有的是列表传参,有的是数组传参,有的可带环境变量,有的不带环境变量。

因为要适配各种各样的应用场景,使用的场景不一样,有些人就喜欢列表传参,有些人喜欢数组传参。所以就配备了这么多接口,这就好比我们 C++ 函数重载的思想。
那为什么 execve 是单独的呢?

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

 它处于 man 2 号手册,execve 才属于是真正意义上的系统调用接口。

 总结一下它们的命名规律,通过这个来记忆对应接口的功能会好很多:

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

感谢观看!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【解决复杂链式任务,打造全能助手】LangChain 大模型 打造 钢铁侠的全能助理 Jarvis

LangChain 大模型 结合 做 AutoGPT、ChatPDF 思维链 CoTLangChain模型IO&#xff1a;和大模型交互、提示词模版数据连接&#xff1a;从数据的接入、分割&#xff0c;到向量的构建、存储、搜索链&#xff1a;串联和组织&#xff0c;多个语言模型、组件记忆&#xff1a;灵魂伴侣&…

C#中使用 async await TaskCompletionSource<T>实现异步逻辑同步写

Task、async 和 await 是 C# 中用于处理异步编程的关键概念。它们一起构成了异步编程的基础。 Task Task 是表示异步操作的抽象&#xff0c;它属于 System.Threading.Tasks 命名空间。Task 可以表示已经完成的任务、正在运行的任务或者尚未开始的任务。通过 Task&#xff0c;…

算法导论复习——CHP16 贪心算法

定义 每一步都做出当前看来最优的操作。 问题引入——活动选择问题 问题描述 活动选择问题就是对给定的包含n个活动的集合S&#xff0c;在已知每个活动开始时间和结束时间的条件下&#xff0c;从中选出最多可兼容活动的子集合&#xff0c;称为最大兼容活动集合。 不失一般性&a…

【C++入门】C++内存管理

目录 前言 C/C内存分布 C内存管理方式 1. new和delete操作内置类型 快速了解与使用 2. new和delete操作自定义类型 3. operator new与operator delete 4. operator new [ ] *5.定位new 6. malloc/free和new/delete的区别 总结 前言 C作为一种面向对象的编程语言&#xff…

AI:110-基于深度学习的药物分子结构生成与预测

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

Unity ab包如何加密

「ab包」全称为 AssetBundle &#xff0c;是Unity提供的一种资源存储压缩包。其中储存了游戏的资源&#xff0c;如图片、模型、纹理、音视频、代码等文件。 由于ab包具有灵活储存、支持热更、包体较小且便于管理等优势&#xff0c;已经成为了市面上主流的游戏资源压缩方式。 …

Jmeter(七) - 从入门到精通 - 建立数据库测试计划实战<MySQL数据库>(详解教程)

1.简介 在实际工作中&#xff0c;我们经常会听到数据库的性能和稳定性等等&#xff0c;这些有时候也需要测试工程师去评估和测试&#xff0c;上一篇文章宏哥主要介绍了jmeter连接和创建数据库测试计划的过程,宏哥在文中通过示例和代码非常详细地介绍给大家&#xff0c;希望对各…

【中小型企业网络实战案例 七】配置限速

相关学习文章&#xff1a; 【中小型企业网络实战案例 一】规划、需求和基本配置 【中小型企业网络实战案例 二】配置网络互连互通【中小型企业网络实战案例 三】配置DHCP动态分配地址 【中小型企业网络实战案例 四】配置OSPF动态路由协议【中小型企业网络实战案例 五】配置可…

51单片机(STC8)-- GPIO输入输出

文章目录 I/O口相关寄存器端口数据寄存器端口模式配置寄存器&#xff08;PxM0&#xff0c;PxM1&#xff09;端口上拉电阻控制寄存器(PxPU)关于I/O的注意事项 配置I/O口I/O设置demoI/O端口模式LED控制&#xff08;I/O输出&#xff09;按键检测&#xff08;I/O输入&#xff09; S…

【模拟量采集1.2】电阻信号采集

【模拟量采集1.2】电阻信号采集 1 怎么测&#xff1f;2 测输入电阻电压即转为测模拟电压值&#xff0c;这里需要考虑选用怎样的辅助电阻&#xff1f;3 实际电路分析3.1 在不考虑 VCC-5V 电压的纹波等情况时&#xff08;理想化此时输入的 VCC 就是稳定的 5V&#xff09;3.2 若考…

拖拽式工作流好用吗?有何特点?

大家都知道&#xff0c;随着行业的进步和发展&#xff0c;低代码技术平台也迎来了蓬勃发展期。很多企业喜欢使用低代码实现提质增效的办公效果&#xff0c;拖拽式工作流是其中一个功能&#xff0c;是助力企业实现流程化办公的得力助手。那么&#xff0c;拖拽式工作流好用吗&…

robots.txt

####什么是robots.txt? ​ robots.txt是一个协议,我们可以把它理解为一个网站的"管家",它会告诉搜索引擎哪些页面可以访问,哪些页面不能访问。也可以规定哪些搜索引擎可以访问我们的网站而哪些搜索引擎不能爬取我们网站的信息等等,是网站管理者指定的"君子协议…

FMQL BOOT.bin固化文件生成及固化流程记录

FMQL BOOT.bin固化文件生成及固化流程记录 一、概述 此篇记录上海复旦微JFMQL15T开发板 烧录固化文件BOOT.bin生成及固化操作流程。 以上一篇文章FQML_AXI_GPIO工程构建调试记录 中的工程为基础&#xff0c;做更改。 二、vivado工程配置 2.1新建工程 打开FQML_AXI_GPIO工程…

Unity | Shader基础知识番外(向量数学知识速成)

目录 一、向量定义 二、计算向量 三、向量的加法&#xff08;连续行走&#xff09; 四、向量的长度 五、单位向量 六、向量的点积 1 计算 2 作用 七、向量的叉乘 1 承上启下 2 叉乘结论 3 叉乘的计算&#xff08;这里看不懂就百度叉乘计算&#xff09; 八、欢迎收…

视频号小店全新赛道,新手如何入驻?

我是电商珠珠 视频号小店为视频号团队所研发。距今为止也才发展了一年时间&#xff0c;在23年下半年掀起了不小的浪花。 我做视频号小店也有一年时间了&#xff0c;在他刚开始三个月的时候&#xff0c;就开始带着团队一起做。到现在也拥有了自己的视频号小店运营团队&#xf…

数模学习day06-主成分分析

主成分分析(Principal Component Analysis,PCA)主成分分析是一种降维算法&#xff0c;它能将多个指标转换为少数几个主成分&#xff0c;这些主成分是原始变量的线性组合&#xff0c;且彼此之间互不相关&#xff0c;其能反映出原始数据的大部分信息。一般来说当研究的问题涉及到…

P5534 【XR-3】等差数列————C++、C

目录 【XR-3】等差数列题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 解题思路Code运行结果 【XR-3】等差数列 题目描述 小 X 给了你一个等差数列的前两项以及项数&#xff0c;请你求出这个等差数列各项之和。 等差数列&#…

JavaWeb——后端之Mybatis

四、Mybatis 概念&#xff1a; Mybatis是一款持久层&#xff08;Dao层&#xff09;框架&#xff0c;用于简化JDBC&#xff08;Sun操作数据库的规范&#xff0c;较繁琐&#xff09;的开发 历史&#xff1a; Apache的一个开源项目iBatis&#xff0c;2010年由apache迁移到了goog…

Spring见解

1.Spring概述 1.1.Spring介绍 Spring是轻量级Java EE应用开源框架&#xff08;官网&#xff1a; http://spring.io/ &#xff09;&#xff0c;它由Rod Johnson创为了解决企业级编程开发的复杂性而创建 1.2.简化应用开发体现在哪些方面&#xff1f; IOC 解决传统Web开发中硬编…