C语言进程的相关操作
C语言进程的相关操作
进程简介
- 每个进程都有一个非负整数形式到的唯一编号,即PID(Process Identification,进程标识)
- PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可以为其他进程使用
- 进程的PID由系统内核根据延迟重用算法生成,以确保新进程的PID不同于最近终止进程到的PID
- 其中0号进程,叫做交换进程,系统内核中的一部分,所有进程的根进程,磁盘上没有它的可执行文件
- 1号进程是init进程,在系统自举过程结束时由调度进程创建,读写与系统相关的初始化文件,引导系统至一个特定状态,以超级用户特权运行的普通进程,永不终止
- 除去调度进程以外,系统中的每个进程都有一个唯一的父进程,对任何一个子进程而言,其父进程的PID即是它的PPID
- 下面这些函数都包含在
unistd.h头文件中 pid_t getpid(void);返回调用进程的PIDpid_t getppid(void);返回调用进程的父进程的PIDuid_t getuid(void);返回调用进程的实际用户IDgid_t getgid(void);返回调用进程的实际组IDuid_t geteuid(void);返回调用进程的有效用户IDgid_t getegid(void);返回调用进程的有效组ID
创建子进程
-
创建子进程的函数包含在
unistd.h头文件中 -
fork函数pid_t fork(void);- 功能:创建调用进程的子进程
- 返回值:失败返回-1,成功情况下返回的变量在父进程中是PID,在子进程中是0
- 可以通过这个返回值来执行父进程和子进程
- 当系统中的总的线程数达到了上限,或者用户的总进程达到了上限,
fork函数会失败。
-
创建子进程示例代码
#include <stdio.h> #include <unistd.h>int main(void) {printf("haha\n");// 创建子进程int pid = fork();printf("heihei\n");return 0; }/* haha heihei heihei */
父子进程间的关系
-
以下是父子进程中数据相关
copy的示例图

-
验证上图
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h>int global = 100; // 父进程全局变量->数据区int main(void) { int local = 200; // 父进程局部变量->栈区 int *heap = malloc(sizeof(int)); // 动态分配内存->堆区 *heap = 3;printf("父进程第一次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap); // 创建子进程 pid_t pid = fork(); if(pid == 0) { // 子进程操作,数据会从父进程copy一份过来,这里执行++操作 printf("子进程打印: PID->%d PPID->%d %p->%d %p->%d %p->%d\n", getpid(), getppid(), &global, ++global, &local, ++local, heap, ++*heap); return 0; } sleep(1); // 这里等1s,让子进程++ printf("父进程第二次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap);return 0; }/* 父进程第一次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3 子进程打印: PID->1674605 PPID->1674604 0x5577e1acc010->101 0x7ffd4d4bfaa8->201 0x5577e23422a0->4 父进程第二次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3这里的父进程和子进程地址一样是虚拟地址里面一样,因为每个进程都有一个独立的虚拟地址池,相互不影响的 发现子进程跟父进程互相不影响,验证了上图的案例 */ -
父子进程操作文件,其实是共享一个文件表项的

-
验证上图
#include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <time.h>int main(void) {// 父进程打开文件int fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);if(fd == -1){perror("open");return -1;}// 父进程写入数据char *data = "hello bhlu!";if(write(fd, data, strlen(data)) == -1){perror("write");return -1;}// 创建子进程pid_t pid = fork();if(pid == 0){// 子进程修改文件读写位置if(lseek(fd, -5, SEEK_END) == -1){perror("lseek");return -1;}return 0;}// 再次插入数据,验证子进程修改的读写位置是否生效sleep(1); // 先等1s,让子进程执行完data = "linux\n";if(write(fd, data, strlen(data)) == -1){perror("write");return -1;}// 关闭文件close(fd);return 0; }/* cat test.txt hello linux 发现是修改成功的,说明上图是对的,子进程和父进程共用一个文件表项 */
进程的终止
以下内容只是简单的介绍进程的终止,以便理解
-
进程的终止分为两种
-
正常终止:分为三种情况
-
main函数中正常返回
-
使用
exit函数终止:exit函数可以在任何函数中执行令进程结束,return语句只有在main函数中执行才能令进程结束#include <stdlib.h>void exit(int status); /* 功能: 令进程终止 参数: status 进程的退出码,相当于main函数的返回值 无返回值 *//* exit函数在终止前会做以下几件收尾工作 1. 调用实现通过atexit或on_exit函数注册的函数退出函数 2. 冲刷并关闭所有仍处于打开状态的标准I/O流 3. 删除所有通过tmpfile函数创建的临时文件 4. 执行_exit(status); 使用exit函数令进程终止,通常使用EXIT_SUCCESS和EXIT_FAILUR两个宏 EXIT_SUCCESS -> 1; EXIT_FAILUR -> 0; */ -
调用
_exit/_Exit函数令进程终止// _exit函数 #include <unistd.h>void _exit(int status); /* 参数: status 进程的退出码,相当于main函数的返回值 无返回值 */// _Exit函数 #include <stdlib.h>void _Exit(int status); /* 参数: status 进程的退出码,相当于main函数的返回值 无返回值 *//* _exit函数在终止前会做以下几件收尾工作 1. 关闭所有仍处于打开状态的文件描述符 2. 将调用进程的所有子进程托付过init进程 3. 向调用进程的父进程发送SIGCHLD(7)信号 4. 令调用进程终止运行,将status的低八位作为退出码保存在其终止状态中 */
-
-
异常终止
-
进程执行了系统认为具有危险性的操作时,或者系统本身发生故障或意外,内核会向进程发送特定的信号
SIGILL(4) -> 进程试图执行非法指令 SIGBUS(7) -> 硬件或对齐错误 SIGEPE(8) -> 浮点异常 SIGSEGV(11) -> 无效内存访问 SIGPWR(30) -> 系统供电不足 -
人为触发信号
SIGINT(2) -> Ctrl+c SIGQUIT(3) -> Ctrl+\ SIGKILL(9) -> 不能被捕获或忽略的进程终止信号 SIGTERM(15) -> 可以被捕获或忽略的进程终止编号 -
向进程自己发送信号
#include <stdlib.h>void abort(void); /* 功能: 想进城发送SIGABRT(6)信号,该信号默认情况下可以使进程结束 无返回值 */
-
-
在使用
exit函数或main函数正常退出时,如果注册了atexit或on_exit,那就会触发退出函数,以下是示例代码#include <stdio.h> #include <stdlib.h>void func(void) {exit(6); }void goto1(void) {printf("goto1\n"); }void goto2(int status, void *arg) {printf("status = %d\n", status);printf("arg = %s\n", (char *)arg); }int main(void) {atexit(goto1); // 退出之前执行goto1on_exit(goto2, "heihei"); // 退出之前执行goto2,可以传参func();return 0; }/* 相当于钩子函数,在退出之前执行,可以进行一些回收操作 status = 6 arg = heihei goto1 */
回收子进程
- 如果不回收子进程的话,会导致有很多僵尸进程的存在,从而消耗更多的系统资源。
- 父进程需要等待子进程到的终止,以继续后续工作
- 父进程需要了解子进程终止的原因,是正常终止,还是异常终止
阻塞回收
-
wait函数是用于回收子进程的一个函数,它使用的是阻塞回收,使用它必须包含sys/wait.h头文件 -
wait函数-
pid_t wait(int *status);-
功能:等待和回收任意子进程
-
参数:
status用于输出子进程的终止状态,可置NULL-
补充:可以使用以下工具宏分析子进程的终止状态
if(WIFEXITED(status))// 真printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status)); else// 假printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status));// 下面跟上面判断条件相反 if(WIFSIGNALED(status))// 真printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status)); else// 假printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status));
-
-
返回值:成功返回回收的子进程PID,失败返回-1
-
-
-
简单代码示例
// 子进程的回收 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h>int main(void) {// 创建子进程pid_t pid = fork();if(pid == -1){perror("fork");return -1;}// 子进程相关操作if(pid == 0){printf("%d进程: 我是子进程!\n", getpid());// sleep(5);// exit(3);// _exit(5);// return 2;// abort(); // 向进程发送信号异常结束// 以下两句会造成内存无效访问,会返回11char *p = NULL;*p = 123;}// 父进程等待回收子进程printf("%d进程: 我是父进程!\n", getpid());int s; // 用来输出所回收的子进程终止状态pid_t childpid = wait(&s);if(childpid == -1){perror("wait");return -1;}printf("父进程回收了%d进程的僵尸!\n", childpid);// 根据返回值判断子进程是否是正常结束if(WIFEXITED(s))printf("正常结束: %d\n", WEXITSTATUS(s));elseprintf("异常结束: %d\n", WTERMSIG(s));return 0; } -
以下代码是一个循环创建5个进程,然后父进程挨个回收
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <errno.h>int main(void) {printf("%d进程: 我是父进程!\n--------------------------\n", getpid());sleep(1);// 创建子进程for(int i = 0; i < 5; i++){pid_t pid = fork();if(pid == -1){perror("fork");return -1;}// 子进程操作if(pid == 0){printf("%d进程: 我是子进程!\n", getpid());sleep(i+1);return i+1;}}// 父进程操作: 回收子进程while(1){int s; // 用户接收子进程的终止状态pid_t childpid = wait(&s);if(childpid == -1){if(errno == ECHILD){printf("没有子进程可以回收了!\n");break;}else{perror("wait");return -1;}}// 判断子进程的终止状态if(WIFEXITED(s))printf("正常结束: %d\n", WEXITSTATUS(s));elseprintf("异常终止: %d\n", WTERMSIG(s));}return 0; }
非阻塞回收
-
waitpid函数一般用于非阻塞回收子进程,还可以回收特定子进程,使用这个函数需要引用sys/wait.h头文件 -
waitpid函数pid_t waitpid(pid_t pid, int *status, int options);- 功能:等待并回收任意或特定子进程
- 参数
pid:取-1等待并回收任意子进程,相当于wait函数,>0等待回收特定子进程status:用于输出子进程的终止状态,可置NULLoption:0代表阻塞模式,WNOHANG代表非阻塞模式,如果等待的进程还在运行,则返回0
- 返回值:成功返回回收子进程的PID或者0,失败返回-1
-
以下是使用非阻塞回收的方法回收子进程
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <errno.h>int main(void) {printf("%d进程: 我是父进程!\n-------------------------\n", getpid());// 创建子进程for(int i = 0; i < 5; i++){pid_t pid = fork();if(pid == -1){perror("fork");return -1;}// 子进程相关操作if(pid == 0){printf("%d进程: 我是子进程!\n", getpid());sleep(i+1);// 三种效果if(i == 3){abort();}else if(i == 4){char *p = NULL;*p = 123;}else{return i+1;}}}// 父进程回收子进程sleep(1);while(1){int s; // 用于保存进程的终止状态pid_t childpid = waitpid(-1, &s, WNOHANG); // 这里使用的是非阻塞模式if(childpid == -1){// 报错或者没有子进程了if(errno == ECHILD){printf("没有子进程了!\n");break;}else{perror("waitpid");return -1;}}else if(childpid == 0){// 子进程还在运行printf("子进程在运行,无法回收,先睡会!\n");sleep(2);}else{// 回收成功并判断是否正常终止printf("%d子进程回收成功!\n", childpid);if(WIFEXITED(s))printf("%d进程正常终止, 进程退出码: %d\n\n", childpid, WEXITSTATUS(s));elseprintf("%d进程异常终止, 终止进程信号: %d\n\n", childpid, WTERMSIG(s));}}return 0; }/* 代码执行效果 1761797进程: 我是父进程! ------------------------- 1761798进程: 我是子进程! 1761799进程: 我是子进程! 1761800进程: 我是子进程! 1761801进程: 我是子进程! 1761802进程: 我是子进程! 子进程在运行,无法回收,先睡会! 1761798子进程回收成功! 1761798进程正常终止, 进程退出码: 11761799子进程回收成功! 1761799进程正常终止, 进程退出码: 2子进程在运行,无法回收,先睡会! 1761800子进程回收成功! 1761800进程正常终止, 进程退出码: 31761801子进程回收成功! 1761801进程异常终止, 终止进程信号: 6子进程在运行,无法回收,先睡会! 1761802子进程回收成功! 1761802进程异常终止, 终止进程信号: 11没有子进程了! */
补充
- 实际情况下,无论进程是正常终止还是异常终止,都会通过系统内核向其父进程发送一个SIGCHLD(17)信号,我们可以提供一个针对该信号的处理函数,在信号处理函数中异步的方式回收子进程,这样不仅流程简单,回收效率还高,僵尸进程的存活时间也会很短。
创建新进程
与fork函数不同,这里使用的exec函数是创建一个新的进程,新进程会取代调用自身的进程,新进程覆盖之前的进程地址空间,进程的PID不会改变。

-
exec不是一个函数,而是一堆函数,功能一样,用法相似 -
#include <unistd.h>-
int execl(const char *path, const char *arg, ...);execl("/bin/ls", "ls", "-a", "-l", NULL); /* path使用的是路径名 使用NULL作为arg的结尾 失败返回-1,成功不返回 */ -
int execlp(const char *file, const char *arg, ...);execlp("ls", "ls", "-a", "-l", NULL); /* file使用的是文件名,会从环境变量中一个个的找 使用NULL作为arg的结尾 失败返回-1,成功不返回 */ -
int execle(const char *path, const char *arg, ..., char *const envp[]);char *envp[] = {"NAME=bhlu", "AGE=25", NULL}; execle("/usr/bin/env", "env", NULL, envp); /* 比excel多一个envp,用于设置环境变量,它设置什么,新进程的环境变量就只有什么 失败返回-1,成功不返回 环境变量输出:NAME=bhluAGE=25 */ -
int execv(const char *path, char *const argv[]);char *argv[] = {"ls", "-a", "-l", NULL}; execv("/bin/ls", argv); /* execv系列使用的都是字符指针数组,字符数组是以NULL结尾 失败返回-1,成功不返回 */ -
int execvp(const char *file, char *const argv[]);char *argv[] = {"ls", "-a", "-l", NULL}; execvp("ls", argv); /* 跟execv差不多,就第一个参数是文件名 失败返回-1,成功不返回 */ -
int execve(const char *path, char *const argv[], char *const envp[]);char *argv[] = {"env", NULL}; char *envp[] = {"NAME=bhlu", "AGE=25", NULL}; execve("/usr/bin/env", argv, envp); /* 跟execle函数差不多,就是这里的第二个参数是字符指针数组 失败返回-1,成功不返回 */
-
-
后缀不同,代码的含义也不同
l:即list,新进程的命令以字符指针列表形式传入,列表以空指针结束p:即path:第一个参数,不包含/,就根据PATH环境变量搜索文件e:即environment:设定环境变量,不指定则从调用进程复制v:即vector:新进程的命令行参数以字符指针数组的形式传入,数组以空指针结束- 实际底层最后使用的都是
execve函数
-
使用
exec函数基本会将原进程的所有信号、属性、数据等都丢失或者恢复初识状态,只有PID、PPID、UID等会被继承下来。 -
一般都会先创建一个子进程,然后在子进程中使用
exec函数,以下是相关示例#include <stdio.h> #include <unistd.h>int main(void) {// 创建子进程pid_t pid = fork();if(pid == -1){perror("fork");return -1;}// 子进程相关操作if(pid == 0){char *argv[] = {"env", NULL};if(execvp("/bin/env", argv) == -1){perror("execvp");return -1;}}// 父进程操作printf("父进程PID: %d\n", getpid());return 0; }
system
-
下面介绍的是
c语言执行shell命令的函数 -
#include <stdlib.h>int system(const char *command);- 功能:执行
shell命令 - 参数:
shell命令,如果参数取NULL,返回非0表示Shell可用,返回0表示不可用 - 返回值:成功返回
command进程的终止状态, 失败返回-1
- 功能:执行
-
代码实例
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {int s = system("echo $PATH");if(s == -1){perror("system");return -1;}printf("父进程PID: %d\n", getpid());return 0; } -
system函数内部调用了vfork、exec和waitpid等函数,而且它是标准库函数,可以跨平台使用- 如果调用
vfork或waitpid函数出错,则返回-1 - 如果调用
exec函数出错,则在子进程中执行exit(127) - 如果都成功,会从
waitpid获取command进程的终止状态
- 如果调用
相关文章:
C语言进程的相关操作
C语言进程的相关操作 进程简介 每个进程都有一个非负整数形式到的唯一编号,即PID(Process Identification,进程标识)PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可…...
数据结构学习系列之链式栈
链式栈:即:栈的链式存储结构;分析:为了提高程序的运算效率,应采用头插法和头删法;进栈: int push_link_stack(stack_t *link_stack,int data) {if(NULL link_stack){printf("入参合理性检…...
too many session files in /var/tmp
Linux中Too many open files 问题分析和解决_e929: too many viminfo temp files-CSDN博客...
【7.0】打开未知来源安装应用
默认打开未知来源安装应用 frameworks\base\packages\SettingsProvider\res\values\defaults.xml <bool name"def_install_non_market_apps">false</bool>...
安装ipfs-swarm-key-gen
安装ipfs-swarm-key-gen Linux安装go解释器安装ipfs-swarm-key-gen Linux安装go解释器 https://blog.csdn.net/omaidb/article/details/133180749 安装ipfs-swarm-key-gen # 编译ipfs-swarm-key-gen二进制文件 go get -u github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm…...
BASH shell脚本篇5——文件处理
这篇文章介绍下BASH shell中的文件处理。之前有介绍过shell的其它命令,请参考: BASH shell脚本篇1——基本命令 BASH shell脚本篇2——条件命令 BASH shell脚本篇3——字符串处理 BASH shell脚本篇4——函数 在Bash Shell脚本中,可以使用…...
ElementUI之首页导航及左侧菜单(模拟实现)
目录 编辑 前言 一、mockjs简介 1. 什么是mockjs 2. mockjs的用途 3. 运用mockjs的优势 二、安装与配置mockjs 1. 安装mockjs 2. 引入mockjs 2.1 dev.env.js 2.2 prod.env.js 2.3 main.js 三、mockjs的使用 1. 将资源中的mock文件夹复制到src目录下 2. 点击登…...
Java开源工具库使用之Lombok
文章目录 前言一、常用注解1.1 AllArgsConstructor/NoArgsConstructor/RequiredArgsConstructor1.2 Builder1.3 Data1.4 EqualsAndHashCode1.5 Getter/Setter1.6 Slf4j/Log4j/Log4j2/Log1.7 ToString 二、踩坑2.1 Getter/Setter 方法名不一样2.2 Builder 不会生成无参构造方法2…...
uboot启动流程涉及reset函数
一. uboot启动流程中函数 之前了解了uboot链接脚本文件 u-boot.lds。 从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的 _start。 本文了解 一下,uboot启动过程中涉及的 reset 函数。本文继上一篇文章学习,地址如下ÿ…...
端口被占用怎么解决
第一步:WinR 打开命令提示符,输入netstat -ano|findstr 端口号 找到占用端口的进程 第二步: 杀死使用该端口的进程,输入taskkill /t /f /im 进程号( !!!注意是进程号,不…...
python reportlab 生成多页pdf
多页 from reportlab.pdfgen import canvas from reportlab.platypus import (SimpleDocTemplate, Paragraph, PageBreak, Image, Spacer, Table, TableStyle) from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY from reportlab.lib.styles import P…...
word 多级目录的问题
一、多级标题自动编号 --> 制表符 -> 空格 网址: 【Word技巧】2 标题自动编号——将多级列表链接到样式 - YouTube 二、多级列表 --> 正规形式编号 网址:Word 教学 - 定框架:文档格式与多级标题! - YouTube 三、目…...
python使用mitmproxy和mitmdump抓包之拦截和修改包(四)
我认为mitmproxy最强大的地方,就是mitmdump可以结合python代理,灵活拦截和处理数据包。 首先,mitmdump的路径如下:(使用pip3 install mitmproxy安装的情况,参考我的文章python使用mitmproxy和mitmdump抓包…...
邓俊辉《数据结构》→ “2.6.5 二分查找(版本A)”之“成功查找长度”递推式推导
【问题描述】 邓俊辉的《数据结构(C语言版)(第3版)》(ISBN:9787302330646)中,开始于第48页的“2.6.5 二分查找(版本A)”内容在第50页详述了“成功查找长度”的…...
Linux文件查找,别名,用户组综合练习
1.文件查看: 查看/etc/passwd文件的第5行 [rootserver ~]# head -5 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologi…...
【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现【更新中】
【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现 本文介绍基于libsvm代理模型算法的特征排序方法合集,包括: 1.基于每个特征预测精度进行排序(libsvm代理模型) 2.基于相关系数corr的…...
第三章 图标辅助元素的定制
第三章 图标辅助元素的定制 1.认识图表常用的辅助元素 图表的辅助元素是指除了根据数据绘制的图形之外的元素,常用的辅助元素包括坐标轴、标题、图例、网格、参考线、参考区域、注释文本和表格,它们都可以对图形进行补充说明。 上图中图表常用辅…...
【前端】ECMAScript6从入门到进阶
【前端】ECMAScript6从入门到进阶 1.ES6简介及环境搭建 1.1.ECMAScript 6简介 (1)ECMAScript 6是什么 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标ÿ…...
Android Shape设置背景
设置背景时,经常这样 android:background“drawable/xxx” 。如果是纯色图片,可以考虑用 shape 替代。 shape 相比图片,减少资源占用,缩减APK体积。 开始使用。 <?xml version"1.0" encoding"utf-8"?…...
什么是GraphQL?它与传统的REST API有什么不同?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是GraphQL?⭐ 与传统的REST API 的不同⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
深入解析光敏传感技术:嵌入式仿真平台如何重塑电子工程教学
一、光敏传感技术的物理本质与系统级实现挑战 光敏电阻作为经典的光电传感器件,其工作原理根植于半导体材料的光电导效应。当入射光子能量超过材料带隙宽度时,价带电子受激发跃迁至导带,形成电子-空穴对,导致材料电导率显著提升。…...
用js实现常见排序算法
以下是几种常见排序算法的 JS实现,包括选择排序、冒泡排序、插入排序、快速排序和归并排序,以及每种算法的特点和复杂度分析 1. 选择排序(Selection Sort) 核心思想:每次从未排序部分选择最小元素,与未排…...
