【Linux】18.Linux进程控制(2)
文章目录
- 3. 进程程序替换
- 3.1 单进程版 -- 看看程序替换
- 3.2 替换原理
- 3.3 替换函数
- 函数解释
- 命名理解
- 3.4 多进程版 -- 验证各种程序替换接口
- 3.5 自定义shell
3. 进程程序替换
3.1 单进程版 – 看看程序替换
makefile
mycommand:mycommand.cgcc -o $@ $^ -std=c99
.PHONY:clean
clean:rm -f mycommand
mycommand.c
#include <stdio.h>
#include <unistd.h>// 提供execl, getpid等函数
#include <stdlib.h>int main(){// 打印exec调用前的进程信息// getpid(): 获取当前进程ID// getppid(): 获取父进程IDprintf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());//这类方法的标准写法// execl函数执行新程序// 参数1 "/usr/bin/ls": 要执行的程序的完整路径// 参数2 "ls": 程序名称(argv[0])// 参数3 "-a": 显示所有文件(包括隐藏文件)// 参数4 "-l": 使用长格式显示// 参数5 NULL: 参数列表结束标志execl("/usr/bin/ls","ls", "-a", "-l", NULL);// 如果exec执行成功,这行代码永远不会被执行// 因为原程序的内容已被ls程序替换printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());return 0;
}
打印结果:
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
before: I am a process, pid:261495, ppid:261085
total 36
drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 20 22:26 .
drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
-rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
-rwxrwxr-x 1 ydk_108 ydk_108 16832 Jan 20 22:26 mycommand
-rw-rw-r-- 1 ydk_108 ydk_108 895 Jan 20 22:26 mycommand.c
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
这个程序被ls
替换了。
一开始这个进程有自己的数据段和代码段,ls
有自己的数据段和代码段,但是这里ls
直接把mycommand
的进程的代码段和数据段在内存中覆盖了,进而把页表中的虚拟地址也更改了。
这个就叫做进程替换,也就是进程替换的基本原理。
3.2 替换原理
用fork
创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec
函数以执行另一个程序。当进程调用一种exec
函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec
并不创建新进程,所以调用exec
前后该进程的id
并未改变。
3.3 替换函数
其实有六种以exec
开头的函数,统称exec
函数:(这六个都是库函数调用接口,他们的区别只是传参的不同)
#include <unistd.h>int execl(const char *path, const char *arg, ...);// 第一个参数是完整路径,第二个参数通常是程序名
int execlp(const char *file, const char *arg, ...);//execlp自己会在默认的PATH系统的环境变量里面找,所以可以不带路径
int execle(const char *path, const char *arg, ...,char *const envp[]);// e 表示可以传递环境变量,最后一个参数是环境变量数组
int execv(const char *path, char *const argv[]);// v 表示参数以数组形式传递,比execl更适合参数数量不确定的情况
int execvp(const char *file, char *const argv[]);// 结合了v(数组)和p(PATH搜索)的特点
int execvpe(const char *file, char *const argv[], char *const envp[]);// 结合了v(数组)、p(PATH搜索)和e(环境变量)的特点
这个和上面6个不一样,这个是系统调用接口,上面6个是库函数调用接口。
int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回
-1
所以
exec
函数只有出错的返回值而没有成功的返回值。
要执行一个程序首先就要找到这个程序,所以这里面所有的
exec
函数的第一个参数都是帮我们绝对如何找到这个程序的。找到这个程序后,要告诉系统如何执行这个程序。要不要涵盖选项?涵盖哪些?
如果
exec*
能够实现系统程序,那么可以实现我们自己的程序吗?可以的。
命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。 (l
和v
不会同时出现)
l(list)
: 表示参数采用列表,一个一个传参
v(vector)
: 参数用数组
p(path)
: 有p
自动搜索环境变量PATH
e(env)
: 表示自己维护环境变量
3.4 多进程版 – 验证各种程序替换接口
#include <stdio.h>
#include <unistd.h>// 提供execl, getpid等函数
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main(){pid_t id =fork();if(id == 0){//childprintf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());sleep(5);execl("/usr/bin/ls","ls", "-a", "-l", NULL);printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());exit(0);}//fatherpid_t ret = waitpid(id, NULL, 0);if(ret > 0){printf("wait success, father pid:%d, ret id:%d\n",getpid(), ret);}sleep(5);return 0;
}
运行结果:
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
before: I am a process, pid:261679, ppid:261678
total 36
drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 20 22:54 .
drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
-rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
-rwxrwxr-x 1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
-rw-rw-r-- 1 ydk_108 ydk_108 628 Jan 20 22:54 mycommand.c
wait success, father pid:261678, ret id:261679
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
通过命令查看:
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ while :; do ps ajx |head -1 && ps ajx |grep mycommand |grep -v grep; sleep 1;echo"----------";donePPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand261678 261679 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND261085 261678 261678 261085 pts/0 261678 S+ 1001 0:00 ./mycommand
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
echo----------: command not foundPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
echo----------: command not found
^C
程序替换有没有创建新的子进程?
没有,子进程的
pid
没有变。不创建新进程,只进行进程的程序代码和数据的替换结构。
补充知识:
为什么
after
后面的代码没有执行呢?因为程序替换后,
after
的printf
的代码已经是老程序的代码了,被ls
覆盖掉了。程序替换成功后,
exit
之后的代码不会执行,那么如果替换失败呢?(例如路径写错,命令不存在)那么程序就会继续往后走。所以
exit*
函数只有失败有返回值,没有成功的返回值。我们的
CPU
怎么知道新程序应该进入的入口地址呢?
Linux
中形成的可执行程序是有格式的,EIF
,是有可执行程序的表头的,可执行程序的入口就在表中。实际上不只是可以调用
ls
命令,还可以调用自己写的C
程序,python
程序,shell
脚本,Java
程序。但是无论是我们的可执行程序还是脚本,为什么能够跨语言调用呢?因为所有的语言运行起来,本质上都是进程。
环境变量是什么时候给进程的呢?
环境变量实际上也是数据。当我们创建子进程的时候,环境变量就已经被子进程继承下去了。所以程序替换中,环境变量信息不会被子进程替换。
如果我想给子进程传递环境变量,应该怎么传递呢?
新增环境变量
直接给
bash
添加环境变量在父进程中使用
putenv()
添加环境变量彻底替换环境变量
使用
execle
的时候把环境变量参数换成自己的
3.5 自定义shell
touch.sh
#!/usr/bin/bashecho "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
ls -a -l
运行:bash touch.sh
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ bash touch.sh
hello 1
hello 1
hello 1
hello 1
hello 1
total 40
drwxrwxr-x 2 ydk_108 ydk_108 4096 Jan 21 00:00 .
drwxrwxr-x 13 ydk_108 ydk_108 4096 Jan 20 22:14 ..
-rw-rw-r-- 1 ydk_108 ydk_108 82 Jan 20 22:16 makefile
-rwxrwxr-x 1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
-rw-rw-r-- 1 ydk_108 ydk_108 628 Jan 20 22:54 mycommand.c
-rw-rw-r-- 1 ydk_108 ydk_108 101 Jan 21 00:00 touch.sh
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
命令行交互的shell代码:
// 包含必要的头文件
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数
#include <assert.h> // 断言
#include <string.h> // 字符串操作
#include <unistd.h> // UNIX标准函数
#include <sys/types.h> // 系统数据类型
#include <sys/wait.h> // 进程等待// 定义shell提示符的格式
#define LEFT "[" // 左边界符号
#define RIGHT "]" // 右边界符号
#define LABLE "#" // 提示符标签
#define DELIM " \t" // 命令分隔符(空格和制表符)
#define LINE_SIZE 1024 // 命令行最大长度
#define ARGC_SIZE 32 // 命令参数最大个数
#define EXIT_CODE 44 // 子进程退出码// 全局变量定义
int lastcode = 0; // 上一条命令的返回值
int quit = 0; // 退出标志
extern char **environ; // 环境变量数组
char commandline[LINE_SIZE]; // 存储命令行
char *argv[ARGC_SIZE]; // 存储解析后的命令参数
char pwd[LINE_SIZE]; // 存储当前工作目录// 自定义环境变量存储空间
char myenv[LINE_SIZE];// 获取当前用户名
const char *getusername(){return getenv("USER");
}// 获取主机名
const char *gethostname(){return getenv("HOSTNAME");
}// 获取当前工作目录
void getpwd(){getcwd(pwd, sizeof(pwd));
}// 实现shell交互界面
void Interact(char *cline, int size){getpwd();// 打印提示符,格式为 [用户名@主机名 当前目录]#printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);// 读取用户输入char *s = fgets(cline, size, stdin);assert(s);(void)s;// 去掉输入字符串末尾的换行符cline[strlen(cline)-1] = '\0';
}// 解析命令行字符串为参数数组
int splistring(char cline[], char *_argv[]){int i = 0;// 获取第一个参数(命令名)argv[i++] = strtok(cline, DELIM);// 获取后续参数while(_argv[i++] = strtok(NULL, DELIM));return i-1; // 返回参数个数
}// 执行外部命令
void NormalExcute(char *_argv[]){pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){ // 子进程// 使用execvp执行命令execvp(_argv[0], _argv);exit(EXIT_CODE);}else{ // 父进程int status = 0;// 等待子进程结束pid_t rid = waitpid(id, &status, 0);if(rid == id){// 保存命令执行结果lastcode = WEXITSTATUS(status);}}
}// 处理内建命令
int buildCommand(char *_argv[], int _argc){// cd命令if(_argc == 2 && strcmp(_argv[0],"cd")==0){chdir(argv[1]); // 改变当前目录getpwd(); // 更新pwdsprintf(getenv("PWD"),"%s", pwd); // 更新PWD环境变量return 1;}// export命令:设置环境变量else if(_argc == 2 && strcmp(_argv[0],"export")==0){strcpy(myenv, _argv[1]);putenv(myenv);return 1;}// echo命令:显示文本或变量值else if(_argc == 2 && strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){ // 显示上一条命令的返回值printf("%d\n",lastcode);lastcode=0;}else if(*_argv[1]=='$'){ // 显示环境变量的值char *val = getenv(_argv[1]+1);if(val) printf("%s\n",val);}else{ // 显示普通文本printf("%s\n",_argv[1]);}return 1;}// 为ls命令添加颜色支持if(strcmp(_argv[0],"ls")==0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0; // 不是内建命令
}int main(){while(!quit){//1.//2.交互问题,获取命令行Interact(commandline, sizeof(commandline));//3.子串分割的问题,解析命令行int argc = splistring(commandline, argv);if(argc == 0) continue;//4.指令的判断,判断是不是内建命令//debug//for(int i=0;argv[i];i++){// printf("[%d]: %s\n",i,argv[i]);//}//内键命令本质上就是shell内部的一个函数。int n = buildCommand(argv, argc);//5.普通命令的执行// 如果不是内建命令,则作为外部命令执行if(!n) NormalExcute(argv);}return 0;
}
打印结果:
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./myshell
[ydk_108@(null) /home/ydk_108/108/lesson17]# ls -a
. .. makefile mycommand mycommand.c myshell myshell.c touch.sh
[ydk_108@(null) /home/ydk_108/108/lesson17]# pwd
/home/ydk_108/108/lesson17
[ydk_108@(null) /home/ydk_108/108/lesson17]#
Segmentation fault
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
所以,当我们进行登陆的时候,系统就是要启动一个shell
进程。
我们shell
本身的环境变量是从哪里来的?
当用户登录的时候,shell
会读取目录用户下的.bash_profile
文件,里面保存了导入环境变量的方式。
相关文章:

【Linux】18.Linux进程控制(2)
文章目录 3. 进程程序替换3.1 单进程版 -- 看看程序替换3.2 替换原理3.3 替换函数函数解释命名理解 3.4 多进程版 -- 验证各种程序替换接口3.5 自定义shell 3. 进程程序替换 3.1 单进程版 – 看看程序替换 makefile mycommand:mycommand.cgcc -o $ $^ -stdc99 .PHONY:clean …...

reactor框架使用时,数据流请求流程
1. 我们在Flux打开时,可以看到 public abstract class Flux<T> implements CorePublisher<T> { 2. public interface CorePublisher<T> extends Publisher<T> {void subscribe(CoreSubscriber<? super T> subscriber); } Publish…...
读西瓜书的数学准备
1,高等数学:会求偏导数就行 2,线性代数:会矩阵运算就行 参考:线性代数--矩阵基本计算(加减乘法)_矩阵运算-CSDN博客 3,概率论与数理统计:知道啥是随机变量就行...

摄像头模块如何应用在宠物产品领域
一、宠物监控类产品 家庭宠物远程监控摄像头 1.基本功能与原理:这类摄像头可以通过 Wi - Fi 连接到家庭网络,主人可以使用手机应用程序在任何有网络连接的地方查看宠物的实时画面。摄像头模块内置有图像传感器,能够捕捉光线并将其转换为数字…...

c++学习第七天
创作过程中难免有不足,若您发现本文内容有误,恳请不吝赐教。 提示:以下是本篇文章正文内容,下面案例可供参考。 一、const成员函数 //Date.h#pragma once#include<iostream> using namespace std;class Date { public:Date…...

Ubuntu 24.04 LTS 通过 docker 安装 nextcloud 搭建个人网盘
准备 Ubuntu 24.04 LTSUbuntu 空闲硬盘挂载Ubuntu 安装 Docker DesktopUbuntu 24.04 LTS 安装 tailscale [我的Ubuntu服务器折腾集](https://blog.csdn.net/jh1513/article/details/145222679。 安装 nextcloud 参考 Ubuntu24.04系统Docker安装NextcloudOnlyoffice _。 更…...

RabbitMQ1-消息队列
目录 MQ的相关概念 什么是MQ 为什么要用MQ MQ的分类 MQ的选择 RabbitMQ RabbitMQ的概念 四大核心概念 RabbitMQ的核心部分 各个名词介绍 MQ的相关概念 什么是MQ MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出&am…...

Open3D计算点云粗糙度(方法一)【2025最新版】
目录 一、Roughness二、代码实现三、结果展示博客长期更新,本文最近更新时间为:2025年1月18日。 一、Roughness 通过菜单栏的Tools > Other > Roughness找到该功能。 这个工具可以估计点云的“粗糙度”。 选择一个或几个点云,然后启动这个工具。 CloudCompare只会询问…...

算法6(力扣148)-排序链表
1、问题 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 2、采用例子 输入:head [4,2,1,3] 输出:[1,2,3,4] 3、实现思路 将链表拆分成节点,存入数组使用sort排序,再用reduce重建链接 4、具…...

一文大白话讲清楚webpack基本使用——9——预加载之prefetch和preload以及webpackChunkName的使用
文章目录 一文大白话讲清楚webpack基本使用——9——预加载之prefetch和preload1. 建议按文章顺序从头看,一看到底,豁然开朗2. preload和prefetch的区别2. prefetch的使用3. preload的使用4. webpackChunkName 一文大白话讲清楚webpack基本使用——9——…...

【大数据2025】MapReduce
MapReduce 基础介绍 起源与发展:是 2004 年 10 月谷歌发表的 MAPREDUCE 论文的开源实现,最初用于大规模网页数据并行处理,现成为 Hadoop 核心子项目之一,是面向批处理的分布式计算框架。基本原理:分为 map 和 reduce …...

Windows图形界面(GUI)-QT-C/C++ - Qt List Widget详解与应用
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 QListWidget概述 使用场景 常见样式 QListWidget属性设置 显示方式 (Display) 交互行为 (Interaction) 高级功能 (Advanced) QListWidget常见操作 内容处理 增加项目 删除项目…...
深度学习python基础(第二节) 分支语句和循环语句
本节主要介绍分支语句和循环语句的基本语法。 注意:在python中的作用域以缩进为准。有语言基础的很好理解,了解语法格式就可以。 布尔类型和比较运算符 # True真,False假 a True print(f"布尔变量a的内容是:{a},类型是:{type(a)}") 比较运算…...

Gin 源码概览 - 路由
本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…...

第6章 ThreadGroup详细讲解(Java高并发编程详解:多线程与系统设计)
1.ThreadGroup 与 Thread 在Java程序中, 默认情况下, 新的线程都会被加入到main线程所在的group中, main线程的group名字同线程名。如同线程存在父子关系一样, Thread Group同样也存在父子关系。图6-1就很好地说明了父子thread、父…...
CentOS 7乱码问题如何解决?
1.使用超级用户操作: sudo su2.修改i18n配置文件: vi /etc/sysconfig/i18n将文件修改或添加为以下内容: LANG"zh_CN.UTF8" LC_ALL"zh_CN.UTF8"保存并退出(按Esc键,输入:wq,然后回车)…...
JavaScript语言的多线程编程
JavaScript语言的多线程编程 JavaScript是一种广泛使用的编程语言,主要用于网页开发。由于其单线程的特性,JavaScript 一直以来都有“无法进行多线程编程”的印象。尽管如此,随着技术的发展,JavaScript也逐渐引入了多线程的概念&…...

OpenSeaOtter使用手册-变更通知和持续部署
我们在OpenSeaOtter Server 0.1.1版本增加的镜像变更通知功能。通过镜像变更通知和OpenSeaOtter Agent就可以轻松获得持续部署能力。 镜像变更通知是通过push的方式下发到Agent的,Agent所在机器不需要外网地址。在Agent收到镜像变更通知后,就会调用对应的…...

(2)STM32 USB设备开发-USB虚拟串口
例程:STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程,没有知识,全是实操,按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的,如果代码中出现一些外设的…...

他把智能科技引入现代农业领域
江苏田倍丰农业科技有限公司(以下简称“田倍丰”)是一家专注于粮油种植的农业科技公司,为拥有300亩以上田地的大户提供全面的解决方案。田倍丰通过与当地政府合作,将土地承包给大户,并提供农资和技术,实现利…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...