深入理解进程:从底层原理到嵌入式实战(3-4 万字详解)

前言:为什么硬件开发者必须吃透进程?

作为嵌入式开发者,你可能会说:“我平时用的 RTOS 里只有任务(Task),没有进程啊!” 但如果你想在珠三角拿到 12k + 的嵌入式开发 offer,尤其是进入智能硬件或汽车电子领域,进程管理是绕不开的硬骨头 ——

  • 智能硬件常需要 Linux 系统跑应用程序,多进程协作是基础

  • 汽车电子的 ECU(电子控制单元)里,RTOS 的任务管理本质是简化的进程管理

  • 面试时,进程相关知识点(如 IPC、调度算法)是大厂必考题

本文将从 “是什么 - 为什么 - 怎么做” 三个维度,用 3-4 万字的篇幅彻底讲透进程。包含 15 + 代码示例、8 张思维导图、10 + 实战案例,保证刷过牛客 100 题的嵌入式开发者都能看懂。

一、进程的本质:从 “死代码” 到 “活程序” 的蜕变

1.1 程序与进程的核心区别(附实例对比)

很多人搞不清 “程序” 和 “进程” 的区别,我们用一个嵌入式场景举例:

程序(Program):你写的led_blink.c编译后生成的led_blink.elf文件,存储在开发板的 Flash 里,这是静态的—— 就像一本菜谱,躺在书架上不会自己做菜。

进程(Process):当你在 Linux 开发板上执行./led_blink,操作系统会把led_blink.elf加载到内存,分配 CPU 时间、GPIO 资源,让代码跑起来 —— 这是动态的,就像厨师按照菜谱实际做菜的过程。

用表格对比关键区别:

对比项 程序(Program) 进程(Process) 嵌入式场景举例
存在形式 静态文件(.elf/.bin) 动态执行过程 Flash 里的固件 vs 运行中的固件
资源占用 不占用 CPU / 内存(仅占磁盘) 占用 CPU、内存、I/O 资源 未运行的 APP vs 后台运行的 WiFi 服务
生命周期 永久存在(除非删除文件) 有创建、运行、终止的过程 下载固件 vs 启动 / 关闭传感器服务
独立性 无(多个程序可共享文件) 独立地址空间、独立资源 多个任务共享 UART vs 进程独占 SPI

实战验证:在 Linux 开发板上执行ls -l /bin/ls(查看程序)和ps -ef | grep ls(查看进程),前者显示文件属性,后者显示运行状态。

1.2 进程的 “三要素”:程序、数据、PCB

一个进程能跑起来,必须具备三个核心要素:

  1. 程序段(Code Segment):存放指令,比如while(1){toggle_led();delay(1000);}

  2. 数据段(Data Segment):存放变量,比如int led_state = 0;(全局变量)、栈上的局部变量

  3. 进程控制块(PCB):操作系统管理进程的 “身份证”,记录进程状态、资源等信息

用思维导图展示三者关系:

进程
程序段
数据段
PCB
机器指令
函数库调用
全局变量
局部变量
常量
进程ID
状态
CPU寄存器
内存指针
打开文件列表

嵌入式视角:在 STM32 的 FreeRTOS 中,任务控制块(TCB)就是简化的 PCB,包含任务栈指针、优先级、状态等信息,对应的数据结构类似:

// FreeRTOS任务控制块(简化版)typedef struct tskTaskControlBlock {    StackType\_t \*pxTopOfStack;  // 栈顶指针(对应PCB的CPU上下文)    xListItem xStateListItem;   // 状态链表项(对应PCB的状态)    UBaseType\_t uxPriority;     // 优先级(对应PCB的调度信息)    // ... 其他资源信息} TCB\_t;

1.3 进程的 5 个核心特征(附反例说明)

进程有 5 个特征,缺一个都不能叫 “进程”:

  1. 动态性:能被创建、调度、终止(反例:ROM 里的固化程序,无法动态调度)

    举例:在 Linux 中用./app &启动进程,kill终止进程,体现动态性。

  2. 并发性:多个进程可同时存在(反例:单任务单片机程序,一次只能跑一个功能)

    举例:开发板上同时运行温度采集进程WiFi上传进程

  3. 独立性:拥有独立地址空间(反例:线程,共享进程地址空间)

    举例:一个进程崩溃(如段错误),不会影响其他进程。

  4. 异步性:进程按不可预知的速度推进(反例:实时任务,需严格按时间执行)

    举例:两个进程打印日志,输出顺序可能每次不同。

  5. 结构性:由程序段、数据段、PCB 组成(反例:裸机程序,没有 PCB 管理)

    举例:Linux 的/proc/[pid]/目录下的文件,就是进程结构的体现。

面试陷阱:面试官可能问 “线程是否具备这些特征?”—— 线程没有独立性(共享地址空间),所以不是进程。

二、进程状态:从 “就绪” 到 “运行” 的生死轮回

2.1 进程的 5 种基本状态(附 Linux 实际验证)

进程在生命周期中会经历 5 种状态,我们结合ps命令的实际输出理解:

状态名称 英文标识 含义(大白话) Linux 中查看方式(ps aux)
创建态 NEW 刚被创建,还没加入就绪队列 一般看不到(持续时间极短)
就绪态 READY 万事俱备,就等 CPU 时间片 R(Running 的缩写,包含就绪)
运行态 RUNNING 正在 CPU 上执行 R
阻塞态 BLOCKED 等资源(如 I/O),主动放弃 CPU S(Sleeping)或 D(深度睡眠)
终止态 TERMINATED 已结束,等待回收 PCB Z(Zombie,僵尸进程)

实战操作:在 Linux 开发板上执行:

\# 启动一个会阻塞的进程(如ping一个不存在的IP)ping 192.168.1.254 &\# 查看状态(会显示S,阻塞在网络I/O)ps aux | grep ping

你会看到ping进程状态为S,表示它因等待网络响应而阻塞。

2.2 状态转换的 6 种场景(附代码触发示例)

进程状态不会凭空变化,每种转换都有明确的触发条件。我们用 “嵌入式传感器采集” 场景举例:

  1. 创建态 → 就绪态

    触发:进程创建完成,资源分配完毕。

    代码示例

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();  // 创建子进程(进入创建态)&#x20;   if (pid == 0) {      // 子进程创建完成,进入就绪态&#x20;       printf("子进程就绪\n");&#x20;   }&#x20;   return 0;}
  1. 就绪态 → 运行态

    触发:调度器选中该进程,分配 CPU。

    场景:就绪队列中只有你的传感器进程,调度器会立即让它运行。

  2. 运行态 → 就绪态

    触发:时间片用完,或被高优先级进程抢占。

    Linux 验证

\# 启动一个占用CPU的进程while true; do :; done &\# 再启动一个高优先级进程(nice值更小)nice -n -5 ./high\_prio\_app &\# 查看第一个进程会变成就绪态(R,但实际未运行)ps -l
  1. 运行态 → 阻塞态

    触发:进程请求 I/O(如读取传感器数据)。

    代码示例

// 读取I2C传感器(会阻塞等待数据)int fd = open("/dev/i2c-1", O\_RDWR);char data\[10];read(fd, data, 10);  // 执行到此处,进程进入阻塞态
  1. 阻塞态 → 就绪态

    触发:等待的资源到了(如传感器数据读取完成)。

    原理:I/O 完成后,硬件会产生中断,内核处理中断时将进程从阻塞队列移到就绪队列。

  2. 运行态 → 终止态

    触发:进程执行完毕,或被 kill。

    代码示例

// 正常终止int main() {&#x20;   printf("任务完成\n");&#x20;   return 0;  // 执行到此处,进程进入终止态}

状态转换思维导图

分配完资源
调度器选中
时间片用完/被抢占
等I/O/信号量
资源就绪
执行完毕/被kill
被强制终止
被强制终止
创建态
就绪态
运行态
阻塞态
终止态

2.3 嵌入式 RTOS 中的状态变种(以 FreeRTOS 为例)

RTOS 的任务状态是进程状态的简化版,但更贴近硬件实际:

FreeRTOS 任务状态 对应进程状态 嵌入式场景举例
就绪态(Ready) 就绪态 等待调度器分配 CPU 的传感器任务
运行态(Running) 运行态 正在采集温湿度的任务
阻塞态(Blocked) 阻塞态 调用 vTaskDelay () 的延时任务
挂起态(Suspended) 无对应 被 vTaskSuspend () 暂停的调试任务

关键区别:RTOS 没有 “僵尸态”,任务删除后资源立即回收(因为嵌入式系统资源有限,不允许浪费)。

代码对比

// FreeRTOS任务状态转换示例void vSensorTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 读取传感器(可能进入阻塞态)&#x20;       read\_sensor();&#x20;      &#x20;&#x20;       // 延时100ms(主动进入阻塞态)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(100));  // 对应进程的阻塞态&#x20;   }}

三、进程控制块(PCB):进程的 “身份证 + 档案袋”

3.1 PCB 的作用:操作系统如何 “记住” 进程?

想象一个场景:你正在用开发板调试程序,突然被打断去接电话,回来后能接着调试 —— 因为你 “记住” 了之前的状态(断点位置、变量值)。

操作系统管理进程也是同理,PCB 就是用来 “记住” 进程状态的结构。没有 PCB,操作系统就无法管理进程

具体来说,PCB 的作用有三个:

  1. 唯一标识:通过 PID 区分不同进程(就像身份证号)。

  2. 状态记录:记录进程当前状态(就绪 / 阻塞等),供调度器参考。

  3. 资源索引:保存进程占用的内存、文件、设备等资源的指针。

类比理解:PCB 就像医院的病历卡 —— 每个病人(进程)一张,记录病情(状态)、检查结果(资源),医生(操作系统)通过病历卡了解病人情况。

3.2 Linux 内核中的 PCB:task_struct 结构体详解

Linux 中的 PCB 是task_struct结构体(定义在linux/sched.h),包含 300 + 字段,我们挑嵌入式开发者必懂的 10 个字段详解:

struct task\_struct {&#x20;   // 1. 进程标识&#x20;   pid\_t pid;                  // 进程ID(唯一标识)&#x20;   pid\_t tgid;                 // 线程组ID(多线程时用)&#x20;  &#x20;&#x20;   // 2. 状态信息&#x20;   volatile long state;        // 进程状态(TASK\_RUNNING等)&#x20;   unsigned int flags;         // 进程标志(如PF\_KTHREAD表示内核线程)&#x20;  &#x20;&#x20;   // 3. 调度信息&#x20;   int prio;                   // 动态优先级&#x20;   int static\_prio;            // 静态优先级&#x20;   struct sched\_entity se;     // 调度实体(用于CFS调度器)&#x20;  &#x20;&#x20;   // 4. 内存信息&#x20;   struct mm\_struct \*mm;       // 内存描述符(用户空间内存)&#x20;   struct mm\_struct \*active\_mm;// 活跃内存描述符(内核线程用)&#x20;  &#x20;&#x20;   // 5. 上下文信息(CPU寄存器)&#x20;   struct thread\_struct thread;// 存放寄存器值(切换时保存/恢复)&#x20;  &#x20;&#x20;   // 6. 父子关系&#x20;   struct task\_struct \*parent; // 父进程指针&#x20;   struct list\_head children;  // 子进程链表&#x20;  &#x20;&#x20;   // 7. 文件信息&#x20;   struct files\_struct \*files; // 打开的文件列表&#x20;  &#x20;&#x20;   // 8. 信号处理&#x20;   struct signal\_struct \*signal; // 信号描述符&#x20;   struct sighand\_struct \*sighand; // 信号处理函数&#x20;  &#x20;&#x20;   // 9. 时间信息&#x20;   cputime\_t utime;            // 用户态CPU时间&#x20;   cputime\_t stime;            // 内核态CPU时间&#x20;  &#x20;&#x20;   // 10. 其他&#x20;   struct task\_struct \*real\_parent; // 实际父进程(被领养前)};

关键字段解析

  1. pid 与 tgid
  • 单进程:pid = tgid

  • 多线程:主线程 pid = tgid,子线程 pid 不同但 tgid 相同

  • 查看方式ps -L -p <pid> 可看到线程的 LWP(轻量级进程 ID,即 pid)

  1. state
  • TASK_RUNNING:运行 / 就绪态

  • TASK_INTERRUPTIBLE:可中断阻塞(如等待键盘输入)

  • TASK_UNINTERRUPTIBLE:不可中断阻塞(如等待磁盘 I/O,ps显示 D)

  • 注意ps命令中 R = 运行 / 就绪,S = 可中断阻塞,D = 不可中断阻塞

  1. mm 与 active_mm
  • 用户进程:mm 指向自己的内存空间

  • 内核线程:mm=NULL,active_mm 指向借用的用户内存

  • 嵌入式意义:内核线程不占用用户内存,适合资源紧张的嵌入式系统

  1. thread_struct
  • 存放 CPU 寄存器值(如 ARM 的 sp、pc、lr 等)

  • 进程切换时,内核会保存当前 thread_struct,加载下一个进程的 thread_struct

  • 举例:当进程因中断切换时,pc(程序计数器)的值会被保存,恢复时从该地址继续执行

3.3 PCB 的组织方式:进程链表与哈希表

操作系统需要快速找到某个进程的 PCB,Linux 用两种数据结构组织:

  1. 双向循环链表
  • 所有 PCB 通过task_structtasks字段链接成链表

  • 遍历所有进程时使用(如ps aux命令)

  • 定义:struct list_head tasks;

  1. 哈希表
  • 通过 PID 快速查找 PCB(pid_hash数组)

  • 时间复杂度 O (1),比遍历链表快

  • 嵌入式优化:嵌入式 Linux 可能精简哈希表大小,减少内存占用

图示

graph LRsubgraph 进程链表A[PCB1(pid=1)] <--> B[PCB2(pid=2)]B <--> C[PCB3(pid=3)]C <--> Aendsubgraph 哈希表(pid_hash)D[哈希桶0] --> AE[哈希桶1] --> BF[哈希桶2] --> Cend

实战查看:在 Linux 内核源码中,init_task是第一个进程(swapper)的 PCB,所有进程都从它衍生:

// 内核启动时创建的第一个进程struct task\_struct init\_task = INIT\_TASK(init\_task);

四、进程创建:从 fork () 到 exec () 的完整流程

4.1 进程创建的 4 个步骤(附内核源码分析)

创建进程就像开分店:总店(父进程)复制一套经营模式(代码),准备新店面(资源),招聘员工(分配 PID),最后开业(加入就绪队列)。

具体步骤:

  1. 分配 PID
  • pidmap位图中找一个未使用的 PID

  • 代码逻辑(简化):

static int alloc\_pid(struct pid\_namespace \*ns) {&#x20;   // 遍历pidmap,找第一个0位&#x20;   for (i = 0; i < PIDMAP\_ENTRIES; i++) {&#x20;       if (pidmap\[i].page) {&#x20;           // 找到空闲PID&#x20;           return pid;&#x20;       }&#x20;   }}
  1. 复制 PCB
  • 调用dup_task_struct()复制父进程的task_struct

  • 关键操作:分配新的内核栈(alloc_thread_info

  • 注意:默认不复制用户内存(用写时复制技术)

  1. 初始化新 PCB
  • 修改 PID、状态等信息(设为 TASK_RUNNING)

  • 清空父进程特有的信息(如信号处理、计时器)

  • 代码片段:

p->pid = alloc\_pid(p->nsproxy->pid\_ns);p->state = TASK\_RUNNING;p->parent = current;  // current是当前进程(父进程)
  1. 加入进程队列
  • 将新 PCB 加入进程链表(list_add(&p->tasks, &init_task.tasks)

  • 加入对应优先级的就绪队列

  • 通知调度器有新进程就绪

4.2 fork () 系统调用:从 “一分为二” 到 “写时复制”

fork()是创建进程的 “瑞士军刀”,我们从用法、原理、优化三个层面解析。

4.2.1 fork () 的基本用法(附嵌入式场景示例)

函数原型

\#include \<unistd.h>pid\_t fork(void);  // 返回值:父进程得到子进程PID,子进程得到0,失败返回-1

嵌入式场景示例:开发板上同时采集温湿度和光照数据:

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>// 采集温度(子进程)void collect\_temperature() {&#x20;   while(1) {&#x20;       printf("温度: 25℃\n");&#x20;       sleep(2); // 模拟2秒采集一次&#x20;   }}// 采集光照(父进程)void collect\_light() {&#x20;   while(1) {&#x20;       printf("光照: 500lux\n");&#x20;       sleep(3); // 模拟3秒采集一次&#x20;   }}int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid < 0) {&#x20;       perror("fork failed");&#x20;       return 1;&#x20;   } else if (pid == 0) {&#x20;       // 子进程:采集温度&#x20;       collect\_temperature();&#x20;   } else {&#x20;       // 父进程:采集光照&#x20;       collect\_light();&#x20;       // 等待子进程(实际中不会在循环里等)&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

运行结果:温度和光照数据交替打印,实现并行采集。

4.2.2 fork () 的 “写时复制”(COW)优化

早期的fork()会完整复制父进程的内存,效率极低(比如父进程有 1GB 内存,复制就要 1GB 空间)。现代操作系统用 “写时复制” 优化:

  • 原理:父子进程共享同一块物理内存,只有当任一进程修改内存时,才复制被修改的部分(页)

  • 好处:创建进程快(不用复制内存),节省内存(未修改的页共享)

图示

graph TDA[父进程内存] -->|fork()| B[共享物理页]B --> C[父进程修改页1]C --> D[复制页1,父进程用新页1]B --> E[子进程未修改]E --> F[子进程仍用共享页]

验证 COW:在 Linux 上用fork()创建子进程后,立即查看内存使用(top命令),会发现父子进程共享大部分内存。

4.2.3 vfork () 与 fork () 的区别(嵌入式必知)

嵌入式系统资源有限,vfork()fork()更轻量,区别如下:

对比项 fork() vfork()
内存共享 写时复制 完全共享(包括栈)
执行顺序 父子进程执行顺序不确定 子进程先执行,父进程阻塞到子进程 exit ()
用途 通用进程创建 子进程立即调用 exec () 的场景
风险 高(子进程修改内存会影响父进程)

嵌入式使用场景:在内存只有 64MB 的嵌入式设备上,用vfork()+execve()启动新程序,比fork()节省内存。

代码示例

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/stat.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = vfork();&#x20;   if (pid == 0) {&#x20;       // 子进程必须调用exec系列函数或exit&#x20;       execl("/bin/ls", "ls", "-l", NULL);&#x20;       \_exit(0); // 如果exec失败,必须exit&#x20;   } else {&#x20;       // 父进程在子进程exit或exec后才执行&#x20;       printf("子进程已执行\n");&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

4.3 exec 系列函数:进程 “改头换面”

fork()创建的子进程与父进程执行相同代码,exec系列函数能让子进程执行新程序(“换代码”)。

常用 exec 函数

函数名 功能 示例
execl() 命令行参数列表传参 execl(“/bin/ls”, “ls”, “-l”, NULL)
execv() 命令行参数数组传参 char *argv[] = {“ls”, “-l”, NULL}; execv(“/bin/ls”, argv)
execlp() 从 PATH 找程序,不用写全路径 execlp(“ls”, “ls”, “-l”, NULL)
execvp() 结合 execv () 和 execlp () 的特点 char *argv[] = {“ls”, “-l”, NULL}; execvp(“ls”, argv)

嵌入式场景:父进程监控传感器,子进程执行不同的处理程序:

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子进程:执行温度处理程序&#x20;       execl("./temperature\_handler", "temperature\_handler", "25", NULL);&#x20;       // 如果exec失败才会执行下面的代码&#x20;       perror("exec failed");&#x20;       \_exit(1);&#x20;   } else {&#x20;       // 父进程:继续监控&#x20;       printf("监控中...\n");&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

注意exec成功后,子进程的代码、数据会被新程序替换,但 PID 不变(还是原来的子进程)。

五、进程终止与资源回收:避免 “僵尸” 横行

5.1 进程终止的 3 种方式(附代码)

进程终止就像 “死亡”,有自然死亡、意外死亡、被杀死三种方式:

  1. 正常终止(自然死亡)
\#include \<stdio.h>\#include \<stdlib.h> // exit()\#include \<unistd.h> // \_exit()int main() {&#x20;   printf("正常终止"); // 没有换行符&#x20;   exit(0); // 会刷新缓冲区,输出"正常终止"&#x20;   // \_exit(0); // 不刷新缓冲区,可能不输出}
  • main()返回(return 0;

  • 调用exit()(会刷新缓冲区)

  • 调用_exit()(不刷新缓冲区,嵌入式常用)

  1. 异常终止(意外死亡)
int main() {&#x20;   int a = 1 / 0; // 会产生SIGFPE信号,异常终止&#x20;   return 0;}
  • 除以零、非法内存访问(段错误)

  • 收到致命信号(如 SIGSEGV、SIGFPE)

  1. 被其他进程杀死(他杀)
\#include \<signal.h>\#include \<stdio.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       while(1) sleep(1); // 子进程死循环&#x20;   } else {&#x20;       sleep(2);&#x20;       kill(pid, SIGKILL); // 父进程杀死子进程&#x20;   }&#x20;   return 0;}
  • 其他进程调用kill()发送信号

  • kill命令(如kill -9 <pid>

5.2 僵尸进程:是什么、为什么、怎么办

5.2.1 僵尸进程的产生(附复现代码)

定义:子进程终止后,PCB 未被回收,变成僵尸进程(Zombie)。

产生原因:父进程未调用wait()waitpid()回收子进程资源。

复现代码

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子进程:立即终止&#x20;       printf("子进程终止\n");&#x20;       \_exit(0);&#x20;   } else {&#x20;       // 父进程:不调用wait(),进入死循环&#x20;       while(1) sleep(1);&#x20;   }&#x20;   return 0;}

查看僵尸进程

\# 编译运行上述程序后ps aux | grep defunct  # defunct表示僵尸进程

会看到子进程状态为Z+(Zombie)。

5.2.2 僵尸进程的危害与解决方法

危害

  • 占用 PID(系统 PID 有限,如 32768 个),僵尸太多会导致无法创建新进程

  • 占用 PCB 内存(每个 PCB 约 1KB,10 万个僵尸就占 100MB)

解决方法

  1. 父进程主动回收:调用wait()waitpid()
\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       \_exit(0);&#x20;   } else {&#x20;       int status;&#x20;       waitpid(pid, \&status, 0); // 等待子进程终止&#x20;       // 可以通过status获取子进程退出状态&#x20;       if (WIFEXITED(status)) {&#x20;           printf("子进程正常退出,返回值:%d\n", WEXITSTATUS(status));&#x20;       }&#x20;   }&#x20;   return 0;}
  1. 父进程忽略 SIGCHLD 信号
\#include \<signal.h>signal(SIGCHLD, SIG\_IGN); // 告诉内核:子进程终止后自动回收
  1. 双重 fork ():让 init 进程领养孙子进程
// 父进程 -> 子进程A -> 子进程B// 子进程A创建B后立即退出,B成为孤儿进程被init领养,init会回收B

5.3 孤儿进程:被 “福利院”(init)领养

定义:父进程先于子进程终止,子进程被 init 进程(PID=1)领养。

特点

  • 无害(init 会负责回收)

  • 状态为S(就绪 / 阻塞),不是僵尸

复现代码

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子进程:等待父进程死亡&#x20;       sleep(2);&#x20;       // 父进程已死,打印新的父进程PID(应为1)&#x20;       printf("子进程的新父进程PID:%d\n", getppid());&#x20;   } else {&#x20;       // 父进程:立即退出&#x20;       \_exit(0);&#x20;   }&#x20;   return 0;}

运行结果:子进程的新父进程 PID 为 1(init 进程)。

六、进程间通信(IPC):让进程 “说话”

进程是独立的,但需要协作(如传感器进程将数据传给上传进程),这就需要 IPC。

6.1 管道(Pipe):最简单的 “传话筒”

管道是最古老的 IPC 方式,像一根 “管子”,数据从一端进,另一端出。

6.1.1 匿名管道(父子进程专用)

特点

  • 半双工(数据单向流动)

  • 只能用于有亲缘关系的进程(父子、兄弟)

  • 基于文件描述符(读端 fd [0],写端 fd [1])

代码示例:父进程给子进程发送传感器数据

\#include \<stdio.h>\#include \<unistd.h>\#include \<string.h>int main() {&#x20;   int fd\[2];&#x20;   // 创建管道&#x20;   if (pipe(fd) == -1) {&#x20;       perror("pipe failed");&#x20;       return 1;&#x20;   }&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子进程:读数据&#x20;       close(fd\[1]); // 关闭写端(只需要读)&#x20;       char buf\[100];&#x20;       read(fd\[0], buf, sizeof(buf));&#x20;       printf("子进程收到:%s\n", buf);&#x20;       close(fd\[0]);&#x20;   } else {&#x20;       // 父进程:写数据&#x20;       close(fd\[0]); // 关闭读端(只需要写)&#x20;       char \*data = "温度:25℃";&#x20;       write(fd\[1], data, strlen(data));&#x20;       close(fd\[1]);&#x20;   }&#x20;   return 0;}

注意

  • 管道有缓冲(默认 64KB),满了会阻塞写操作

  • 读端关闭后写操作会产生 SIGPIPE 信号(默认终止进程)

6.1.2 命名管道(FIFO):任意进程通信

特点

  • 有文件名(在文件系统中可见,如/tmp/myfifo

  • 可用于任意进程(无亲缘关系)

  • 用法类似匿名管道,但需要先创建

创建 FIFO

mkfifo /tmp/sensor\_fifo  # 命令行创建

或代码创建:

\#include \<sys/stat.h>mkfifo("/tmp/sensor\_fifo", 0666); // 0666是权限

通信示例

写进程(传感器采集):

\#include \<stdio.h>\#include \<unistd.h>\#include \<fcntl.h>\#include \<string.h>int main() {&#x20;   int fd = open("/tmp/sensor\_fifo", O\_WRONLY);&#x20;   char \*data = "光照:500lux";&#x20;   write(fd, data, strlen(data));&#x20;   close(fd);&#x20;   return 0;}

读进程(数据处理):

\#include \<stdio.h>\#include \<unistd.h>\#include \<fcntl.h>int main() {&#x20;   int fd = open("/tmp/sensor\_fifo", O\_RDONLY);&#x20;   char buf\[100];&#x20;   read(fd, buf, sizeof(buf));&#x20;   printf("收到:%s\n", buf);&#x20;   close(fd);&#x20;   return 0;}

嵌入式应用:在嵌入式 Linux 中,多个进程(如采集、处理、显示)可通过 FIFO 传递数据,无需考虑进程关系。

6.2 信号(Signal):进程间的 “紧急电报”

信号是异步通知机制,像 “发电报” 一样简单粗暴,适合传递简单指令(如终止、暂停)。

6.2.1 常见信号及默认行为
信号编号 名称 含义 默认行为 嵌入式场景举例
2 SIGINT 中断(Ctrl+C) 终止进程 手动停止调试中的程序
9 SIGKILL 杀死进程 终止进程 强制结束无响应的进程
11 SIGSEGV 段错误(非法内存访问) 终止 + CoreDump 程序 bug 导致内存越界
17 SIGCHLD 子进程终止 忽略 父进程回收子进程
19 SIGSTOP 暂停进程 暂停进程 调试时暂停程序执行

查看所有信号kill -l

6.2.2 发送信号:kill () 函数与 kill 命令

用 kill 命令发送信号

kill -9 1234  # 给PID=1234的进程发SIGKILLkill -SIGSTOP 1234  # 暂停进程

用 kill () 函数发送信号

\#include \<signal.h>\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       while(1) {&#x20;           printf("运行中...\n");&#x20;           sleep(1);&#x20;       }&#x20;   } else {&#x20;       sleep(2);&#x20;       kill(pid, SIGSTOP); // 暂停子进程&#x20;       sleep(2);&#x20;       kill(pid, SIGCONT); // 继续子进程&#x20;       sleep(2);&#x20;       kill(pid, SIGKILL); // 杀死子进程&#x20;   }&#x20;   return 0;}
6.2.3 捕获信号:自定义信号处理函数

进程可以自定义信号的处理方式(除 SIGKILL 和 SIGSTOP,这两个信号不能被捕获)。

代码示例:捕获 SIGINT,实现优雅退出

\#include \<stdio.h>\#include \<signal.h>\#include \<unistd.h>// 信号处理函数void sigint\_handler(int signo) {&#x20;   if (signo == SIGINT) {&#x20;       printf("\n收到中断信号,正在保存数据...\n");&#x20;       // 保存传感器数据等清理工作&#x20;       sleep(1);&#x20;       printf("数据保存完成,退出\n");&#x20;       \_exit(0);&#x20;   }}int main() {&#x20;   // 注册信号处理函数&#x20;   if (signal(SIGINT, sigint\_handler) == SIG\_ERR) {&#x20;       perror("signal failed");&#x20;       return 1;&#x20;   }&#x20;  &#x20;&#x20;   // 模拟传感器采集&#x20;   while(1) {&#x20;       printf("采集数据中...\n");&#x20;       sleep(1);&#x20;   }&#x20;   return 0;}

运行:按 Ctrl+C 时,进程会先保存数据再退出,而不是立即终止。

6.3 共享内存:最快的 IPC(嵌入式首选)

共享内存是效率最高的 IPC 方式 —— 数据直接在内存中共享,无需拷贝。

6.3.1 共享内存的使用步骤
  1. 创建 / 打开共享内存shmget()

  2. 映射到进程地址空间shmat()

  3. 读写共享内存:直接操作指针

  4. 解除映射shmdt()

  5. 删除共享内存shmctl()

代码示例

写进程(传感器):

\#include \<stdio.h>\#include \<sys/ipc.h>\#include \<sys/shm.h>\#include \<string.h>\#define SHM\_SIZE 1024  // 共享内存大小\#define SHM\_KEY 0x1234 // 共享内存键值(唯一标识)int main() {&#x20;   // 1. 创建共享内存&#x20;   int shmid = shmget(SHM\_KEY, SHM\_SIZE, IPC\_CREAT | 0666);&#x20;   if (shmid == -1) {&#x20;       perror("shmget failed");&#x20;       return 1;&#x20;   }&#x20;   // 2. 映射到地址空间&#x20;   char \*shmaddr = shmat(shmid, NULL, 0);&#x20;   if (shmaddr == (void\*)-1) {&#x20;       perror("shmat failed");&#x20;       return 1;&#x20;   }&#x20;   // 3. 写数据&#x20;   strcpy(shmaddr, "温度:25℃ 湿度:60%");&#x20;   printf("写入共享内存: %s\n", shmaddr);&#x20;   // 等待读进程读取&#x20;   sleep(5);&#x20;   // 4. 解除映射&#x20;   shmdt(shmaddr);&#x20;   // 5. 删除共享内存(通常由一个进程负责)&#x20;   shmctl(shmid, IPC\_RMID, NULL);&#x20;   return 0;}

读进程(数据处理):

\#include \<stdio.h>\#include \<sys/ipc.h>\#include \<sys/shm.h>\#define SHM\_SIZE 1024\#define SHM\_KEY 0x1234int main() {&#x20;   // 1. 获取共享内存(已由写进程创建)&#x20;   int shmid = shmget(SHM\_KEY, SHM\_SIZE, 0666);&#x20;   if (shmid == -1) {&#x20;       perror("shmget failed");&#x20;       return 1;&#x20;   }&#x20;   // 2. 映射到地址空间&#x20;   char \*shmaddr = shmat(shmid, NULL, 0);&#x20;   if (shmaddr == (void\*)-1) {&#x20;       perror("shmat failed");&#x20;       return 1;&#x20;   }&#x20;   // 3. 读数据&#x20;   printf("从共享内存读取: %s\n", shmaddr);&#x20;   // 4. 解除映射&#x20;   shmdt(shmaddr);&#x20;   return 0;}
6.3.2 共享内存的同步问题(必知)

共享内存不提供同步机制,多进程同时读写会导致数据错乱(如两个进程同时写同一位置)。

解决方法:用信号量(Semaphore)同步。

示例:用信号量保护共享内存读写:

// 初始化信号量(确保先于共享内存操作)sem\_t \*sem = sem\_open("/sensor\_sem", O\_CREAT, 0666, 1);// 写共享内存前加锁sem\_wait(sem);// 写操作...sem\_post(sem);// 读共享内存前加锁sem\_wait(sem);// 读操作...sem\_post(sem);

嵌入式注意:嵌入式 Linux 可能需要开启CONFIG_SYSVIPC配置才能使用共享内存。

6.4 信号量(Semaphore):进程同步的 “红绿灯”

信号量像 “红绿灯”,控制进程何时可以访问共享资源(如共享内存、硬件设备)。

6.4.1 信号量的基本概念
  • 计数信号量:值可以是任意非负数,用于控制资源数量(如 3 个串口设备)

  • 二元信号量(互斥锁):值只能是 0 或 1,用于互斥访问(如同一时间只能一个进程用 SPI 总线)

P 操作(等待)sem_wait()—— 信号量减 1,若值 < 0 则阻塞

V 操作(释放)sem_post()—— 信号量加 1,唤醒阻塞进程

6.4.2 System V 信号量与 POSIX 信号量

Linux 有两种信号量接口,嵌入式常用 POSIX 信号量(更简单):

POSIX 信号量示例(互斥访问 SPI)

\#include \<semaphore.h>\#include \<stdio.h>\#include \<unistd.h>\#include \<pthread.h>sem\_t sem; // 全局信号量// 模拟SPI操作void spi\_operation(int id) {&#x20;   sem\_wait(\&sem); // P操作:获取锁&#x20;   printf("进程%d开始使用SPI\n", id);&#x20;   sleep(2); // 模拟SPI操作&#x20;   printf("进程%d结束使用SPI\n", id);&#x20;   sem\_post(\&sem); // V操作:释放锁}int main() {&#x20;   // 初始化信号量(1表示互斥锁)&#x20;   sem\_init(\&sem, 0, 1); // 第二个参数0表示线程间共享&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       spi\_operation(2); // 子进程&#x20;   } else {&#x20;       spi\_operation(1); // 父进程&#x20;   }&#x20;   // 销毁信号量&#x20;   sem\_destroy(\&sem);&#x20;   return 0;}

运行结果:两个进程不会同时使用 SPI,体现互斥效果。

6.4.3 信号量解决生产者 - 消费者问题

场景:传感器(生产者)采集数据到缓冲区,处理程序(消费者)从缓冲区取数据。

代码示例

\#include \<semaphore.h>\#include \<stdio.h>\#include \<unistd.h>\#include \<pthread.h>\#define BUFFER\_SIZE 5int buffer\[BUFFER\_SIZE];int in = 0, out = 0;sem\_t empty; // 空缓冲区数量sem\_t full;  // 满缓冲区数量sem\_t mutex; // 互斥锁// 生产者(传感器)void \*producer(void \*arg) {&#x20;   for (int i = 0; i < 10; i++) {&#x20;       int data = i; // 模拟传感器数据&#x20;       sem\_wait(\&empty); // 等空缓冲区&#x20;       sem\_wait(\&mutex);&#x20;       buffer\[in] = data;&#x20;       printf("生产: %d, 位置: %d\n", data, in);&#x20;       in = (in + 1) % BUFFER\_SIZE;&#x20;       sem\_post(\&mutex);&#x20;       sem\_post(\&full); // 满缓冲区+1&#x20;       sleep(1); // 模拟采集间隔&#x20;   }&#x20;   return NULL;}// 消费者(数据处理)void \*consumer(void \*arg) {&#x20;   for (int i = 0; i < 10; i++) {&#x20;       sem\_wait(\&full); // 等满缓冲区&#x20;       sem\_wait(\&mutex);&#x20;       int data = buffer\[out];&#x20;       printf("消费: %d, 位置: %d\n", data, out);&#x20;       out = (out + 1) % BUFFER\_SIZE;&#x20;       sem\_post(\&mutex);&#x20;       sem\_post(\&empty); // 空缓冲区+1&#x20;       sleep(2); // 模拟处理时间&#x20;   }&#x20;   return NULL;}int main() {&#x20;   // 初始化信号量&#x20;   sem\_init(\&empty, 0, BUFFER\_SIZE); // 初始有5个空缓冲区&#x20;   sem\_init(\&full, 0, 0);            // 初始0个满缓冲区&#x20;   sem\_init(\&mutex, 0, 1);           // 互斥锁&#x20;   pthread\_t prod\_tid, cons\_tid;&#x20;   pthread\_create(\&prod\_tid, NULL, producer, NULL);&#x20;   pthread\_create(\&cons\_tid, NULL, consumer, NULL);&#x20;   pthread\_join(prod\_tid, NULL);&#x20;   pthread\_join(cons\_tid, NULL);&#x20;   // 清理&#x20;   sem\_destroy(\&empty);&#x20;   sem\_destroy(\&full);&#x20;   sem\_destroy(\&mutex);&#x20;   return 0;}

运行结果:生产者和消费者交替操作缓冲区,不会出现数据混乱。

七、进程调度:谁先 “上车” 谁说了算

7.1 进程调度的基本概念(嵌入式视角)

进程调度就是 “决定哪个进程先使用 CPU”,像公交车调度 —— 谁先上车、谁后上车,需要规则。

为什么需要调度

  • CPU 是稀缺资源(通常只有 1-4 核)

  • 多个进程需要 “公平” 使用 CPU

  • 不同进程有不同需求(如实时进程需要立即响应)

嵌入式调度 vs 通用 OS 调度

  • 嵌入式:强调实时性(如传感器数据必须 10ms 内处理)

  • 通用 OS:强调公平性和交互性(如桌面系统)

7.2 Linux 的 CFS 调度器(完全公平调度)

Linux 采用 CFS(Completely Fair Scheduler)调度器,核心思想是 “让每个进程获得公平的 CPU 时间”。

7.2.1 CFS 的基本原理
  • 虚拟运行时间:进程实际运行时间按优先级加权后的时间

  • 红黑树:所有就绪进程按虚拟运行时间排序,每次选虚拟运行时间最小的进程

举例

  • 高优先级进程的虚拟时间流逝慢(如实际运行 1ms,虚拟时间 + 0.5ms)

  • 低优先级进程的虚拟时间流逝快(如实际运行 1ms,虚拟时间 + 2ms)

  • 这样高优先级进程能获得更多实际 CPU 时间

7.2.2 进程优先级与 nice 值

Linux 用 nice 值表示进程优先级:

  • 范围:-20(最高优先级)~ 19(最低优先级)

  • 默认值:0

  • 调整优先级:nice -n <值> 命令renice <值> -p <pid>

查看进程 nice 值ps -l(NI 列)

嵌入式应用:在嵌入式系统中,可将实时任务的 nice 值设为 - 20,确保优先执行。

7.3 实时调度策略(嵌入式必备)

嵌入式系统常需要实时调度(如汽车的刹车控制必须立即响应),Linux 提供两种实时调度策略:

  1. SCHED_FIFO
  • 先进先出,一旦获得 CPU 就一直运行,直到主动放弃或被更高优先级进程抢占

  • 适合短时间运行的实时任务(如传感器数据处理)

  1. SCHED_RR
  • 时间片轮转,相同优先级的进程轮流执行

  • 适合需要定期执行的任务(如 10ms 一次的电机控制)

设置实时调度策略

\#include \<stdio.h>\#include \<sched.h>int main() {&#x20;   struct sched\_param param;&#x20;   param.sched\_priority = 50; // 优先级(1-99,值越大优先级越高)&#x20;   // 设置SCHED\_FIFO调度策略&#x20;   if (sched\_setscheduler(0, SCHED\_FIFO, \&param) == -1) {&#x20;       perror("sched\_setscheduler failed");&#x20;       return 1;&#x20;   }&#x20;   // 实时任务...&#x20;   return 0;}

注意:需要 root 权限才能设置实时优先级,嵌入式系统中通常会开启相关配置。

7.4 嵌入式 RTOS 的调度器(以 FreeRTOS 为例)

FreeRTOS 的调度器比 Linux 简单,适合资源有限的嵌入式系统:

  • 抢占式调度:高优先级任务可立即抢占低优先级任务

  • 时间片调度:相同优先级任务轮流执行(可配置)

代码示例

// 高优先级任务(传感器数据处理)void vHighPriorityTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 处理数据(必须快速完成)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(10));&#x20;   }}// 低优先级任务(日志打印)void vLowPriorityTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 打印日志(可延迟)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(100));&#x20;   }}int main() {&#x20;   // 创建任务,高优先级任务优先执行&#x20;   xTaskCreate(vHighPriorityTask, "HighTask", 128, NULL, 2, NULL);&#x20;   xTaskCreate(vLowPriorityTask, "LowTask", 128, NULL, 1, NULL);&#x20;   vTaskStartScheduler(); // 启动调度器&#x20;   return 0;}

关键区别:FreeRTOS 的任务切换开销小(约几微秒),适合微控制器(如 STM32),而 Linux 调度切换开销大(约几十微秒)。

八、实战:用进程知识解决嵌入式实际问题

8.1 案例 1:嵌入式设备的多进程架构设计

以 “智能温湿度传感器” 为例,设计多进程架构:

进程名称 功能 优先级 IPC 方式
采集进程 读取温湿度传感器 共享内存
处理进程 数据校准、转换 共享内存 + 信号量
上传进程 WiFi 上传数据 管道
日志进程 记录系统日志 最低 命名管道

优势

  • 模块化(一个进程出问题不影响其他进程)

  • 可独立升级(如只更新上传进程支持新协议)

  • 方便调试(可单独重启某个进程)

8.2 案例 2:解决传感器数据丢失问题

问题:传感器数据采集快(10ms 一次),但上传慢(100ms 一次),导致数据丢失。

分析:采集进程和上传进程速度不匹配,没有缓冲机制。

解决方案:用共享内存 + 信号量实现环形缓冲区:

  1. 采集进程:将数据写入环形缓冲区,信号量计数 + 1

  2. 上传进程:从缓冲区读数据,信号量计数 - 1

  3. 缓冲区满时,采集进程可选择覆盖旧数据或等待

核心代码:参考 6.4.3 的生产者 - 消费者模型,将缓冲区改为环形。

8.3 案例 3:调试进程相关问题的工具

工具 用途 嵌入式场景示例
ps 查看进程状态 检查是否有僵尸进程(Z 状态)
top/htop 实时查看进程 CPU / 内存使用 发现 CPU 占用 100% 的异常进程
pstree 查看进程树(父子关系) 找到某个进程的父进程
strace 跟踪进程系统调用 调试进程为何无法打开设备文件
gdb attach 调试运行中的进程 在不重启的情况下调试上传进程

调试示例:用strace查看进程为何无法读取传感器:

strace -f ./sensor\_collect  # -f跟踪子进程

会输出所有系统调用,若看到open("/dev/i2c-1", O_RDWR) = -1 ENOENT,说明设备文件不存在。

九、总结:进程知识体系与面试重点

9.1 进程知识体系思维导图

进程
基本概念
状态
PCB
创建与终止
IPC
调度
程序vs进程
进程特征
5种状态及转换
task_struct结构
组织方式
fork/exec
僵尸/孤儿进程
管道/FIFO
信号
共享内存
信号量
CFS调度器
实时调度

9.2 面试高频问题与答案要点

  1. 进程与线程的区别?
  • 进程:资源分配单位,有独立地址空间

  • 线程:调度单位,共享进程资源

  • 开销:进程创建 / 切换开销大,线程小

  1. 僵尸进程产生原因及解决方法?
  • 原因:子进程终止后父进程未回收 PCB

  • 解决:wait ()/waitpid ()、忽略 SIGCHLD、双重 fork ()

  1. 什么是写时复制?为什么用它?
  • 原理:fork () 后父子进程共享内存,修改时才复制

  • 好处:加快进程创建速度,节省内存

  1. 进程间通信方式及优缺点?
  • 管道:简单,仅限亲缘进程

  • 共享内存:最快,需同步

  • 信号量:用于同步,不传递数据

  • 信号:异步,适合简单通知

  1. 实时调度与普通调度的区别?
  • 实时:优先保证响应时间(如 SCHED_FIFO)

  • 普通:优先保证公平性(如 CFS)

9.3 下一步学习建议

  1. 动手实践:用本文代码在开发板上实际运行,观察进程行为

  2. 阅读源码:看 FreeRTOS 的任务调度器源码(理解简化版进程管理)

  3. 项目实战:实现一个多进程的嵌入式应用(如智能家居网关)

  4. 深入内核:学习 Linux 内核进程调度和 IPC 的实现细节

掌握进程知识,不仅能通过面试,更能设计出稳定、高效的嵌入式系统。记住:最好的学习方法是 “用起来”—— 在实际项目中遇到问题、解决问题,才能真正理解进程的精髓。

(注:文档部分内容可能由 AI 生成)

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch 简化指南:GCP Google Compute Engine

作者&#xff1a;来自 Elastic Eduard Martin 系列内容的一部分&#xff1a;开始使用 Elasticsearch&#xff1a;GCP 想获得 Elastic 认证&#xff1f;看看下一期 Elasticsearch Engineer 培训什么时候开始&#xff01; Elasticsearch 拥有丰富的新功能&#xff0c;帮助你根据…

STM32的定时器输入捕获-超声波测距案例

STM32的定时器输入捕获-超声波测距案例 gitee代码输入捕获硬件电路案例说明主函数代码 gitee代码 https://gitee.com/xiaolixi/l-stm32/tree/master/STM32F103C8T6/2-1tem-ld-timer-input-pluse 输入捕获硬件电路 超声波测距案例说明 使用超声波测距传感器使用tim1的输入捕获…

[特殊字符] Spring Boot 常用注解全解析:20 个高频注解 + 使用场景实例

一文掌握 Spring Boot 中最常用的 20 个注解&#xff0c;涵盖开发、配置、Web、数据库、测试等场景&#xff0c;配合示例讲解&#xff0c;一站式掌握&#xff01;&#x1f4cc; 一、核心配置类注解 1. SpringBootApplication 作用&#xff1a;标记为 Spring Boot 应用的入口类&…

【工具变量】地级市城市包容性绿色增长数据(2011-2023年)

城市包容性绿色增长是指在推动城市经济增长的过程中&#xff0c;兼顾环境可持续性、社会公平和包容性发展的理念与实践。它强调在实现绿色转型和低碳发展的同时&#xff0c;保障社会各群体&#xff0c;特别是弱势群体的利益与参与权利&#xff0c;确保增长成果能够公平共享 本…

深入理解React Hooks:从使用到原理

4. 源码解析类:《深入理解React Hooks:从使用到原理》 # 深入理解React Hooks:从使用到原理🔥 **背景**: - Hooks解决了Class组件的哪些问题? - 为什么不能在循环/条件中调用Hooks?🔍 **核心原理**:### 1. Hooks链表 React内部维护一个单向链表:fiber.memoizedSta…

【云原生】Docker 部署 Elasticsearch 9 操作详解

目录 一、前言 二、Elasticsearch 9 新特性介绍 2.1 基于 Lucene 10 重大升级 2.2 Better Binary Quantization(BBQ) 2.3 Elastic Distributions of OpenTelemetry(EDOT) 2.4 LLM 可观测性 2.5 攻击发现与自动导入 2.6 ES|QL 增强 2.7 语义检索 三、基于Docker部署…

uview-ui使用u-search搜索框

1、效果图 2、带地址搜索框&#xff0c;在微信小程序线上需要开启地图定位接口&#xff0c;若没有权限则显示不了城市名&#xff0c;注意事项参考uniapp相关地图 API调用-CSDN博客 <template><view><u-sticky offset-top"-1"><u-search v-mode…

Elasticsearch+Logstash+Kibana部署

目录 一、实验准备 1.下载安装 2.下载java 2.同步主机系统时间 二、部署 1.部署elasticsearch 修改 /etc/elasticsearch/elasticsearch.yml 配置文件 修改 /etc/hosts/ 文件 启动elasticsearch 查看是否启动进程netstat -antptu | grep java 2.部署logstash 进入/et…

TEngine学习

关于静态类中的静态变量赋值&#xff1a; public static class ActorEventDefine{public static readonly int ScoreChange RuntimeId.ToRuntimeId("ActorEventDefine.ScoreChange");public static readonly int GameOver RuntimeId.ToRuntimeId("ActorEventD…

猎板:在 5G 与 AI 时代,印制线路板如何满足高性能需求

5G 与 AI 技术的深度融合&#xff0c;推动电子设备向高速传输、高算力、高集成方向发展&#xff0c;印制线路板&#xff08;PCB&#xff09;作为核心载体&#xff0c;其性能直接决定终端设备的运行效率与可靠性。猎板 PCB 聚焦 5G 通信的高频需求与 AI 算力的密集需求&#xff…

教你如何借助AI精读文献

目录1. 原文2. 对文献有一个快速的理解3. 专业术语解读4. 解答疑问5. 借助AI翻译摘要和引言部分5.1 **摘要 (Abstract)**5.2 **引言 (Introduction)**6. 介绍论文中的“Stack-Propagation”7. 查阅论文里的参考文献&#xff0c;看看他是如何在Introduction中引述研究进展文献&a…

FastAdmin框架超级管理员密码重置与常规admin安全机制解析-卓伊凡|大东家

FastAdmin框架超级管理员密码重置与常规admin安全机制解析-卓伊凡|大东家我们可以看到admin账户是不允许直接修改的&#xff0c;这也是目前fastadmin 框架不允许的&#xff0c;那么如何处理一、FastAdmin超级管理员密码重置方法当FastAdmin的超级管理员密码忘记或需要重置时&am…

我做的基础服务项目,是如何实现 API 安全与限流的(短信、邮件、文件上传、钉钉通知)

我做的基础服务项目&#xff0c;是如何实现 API 安全与限流的&#xff08;短信、邮件、文件上传、钉钉通知&#xff09;一、背景 最近我做了一个基础服务项目&#xff0c;主要对外提供短信、邮件、文件上传和钉钉通知等基础功能。这些接口是多个业务系统都要调用的&#xff0c;…

(Python)类和类的方法(基础教程介绍)(Python基础教程)

源代码&#xff1a;class Students:stats"大学"def __init__(self,name,age,sex,credit):self.namenameself.ageageself.sexsexself.creditcreditdef tell(self):return f"{self.name}说&#xff1a;你好"class Teachers(Students):stats"教师"d…

网络智能体研究综述

网络智能体研究综述1.什么是网络智能体1.1.核心特征1.2.分类方式1.2.1.按功能定位1.2.2. 按网络结构1.2.3.按应用场景1.3.典型应用场景1.4.技术基础1.5.发展趋势与挑战1.5.1.发展趋势1.5.2.核心挑战2.网络智能体盘点3.阿里的WebSailor3.1.WebSailor的主要功能和技术特点3.2.技术…

git 介绍与使用教程

Git 是一个 分布式版本控制系统&#xff0c;每个开发者都有一个完整的本地仓库&#xff08;包含完整历史记录&#xff09;&#xff0c;而远程仓库&#xff08;如 GitHub、GitLab、Gitee&#xff09;是团队共享的中央仓库。它们的关系如下&#xff1a;本地仓库&#xff08;Local…

[AI风堇]基于ChatGPT3.5+科大讯飞录音转文字API+GPT-SOVITS的模拟情感实时语音对话项目

[AI风堇]最近利用工作日的晚间和周末时间&#xff0c;我完成了一个有趣的Python编程小项目。这个项目的灵感来源于上个月在B站看到的"科技怪咖"UP主分享的一个视频&#xff0c;视频中展示了一个名为"DataMagic"的自动化数据处理工具&#xff0c;能够智能分…

物联网-规则引擎的定义

构建物联网系统中的规则引擎是一个系统性的工程&#xff0c;它需要处理来自海量设备的实时数据流&#xff0c;并根据预定义的逻辑触发动作。以下是构建一个高效、可靠、可扩展的物联网规则引擎的关键步骤和考虑因素&#xff1a; 核心目标 实时性&#xff1a; 快速处理设备事件并…

SIMATIC WinCC Unified 使用 KPI 优化流程

大家好&#xff0c;我是东哥说-MES基本知识 33.1 KPI组态简介现有工厂结构表示在面向对象的组态中定义标准化 KPI 概念的起点。 可通过在工厂视图中用作实例的工厂对象类型来映射工厂的各组件。在“性能指 标”(Performance indicators) 全局编辑器中&#xff0c;可定义全局操作…

机器学习-多重线性回归和逻辑回归

目录 1. 多重线性回归 1.1 多元线性回归 1.2 向量化&#xff08;矢量化&#xff09; 1.3 多元线性回归的梯度下降算法 1.4 正规方程 2. 特征缩放 2.1 特征缩放 2.2 检查梯度下降是否收敛 2.3 学习率的选择 2.4 特征工程 2.5 多项式回归 3. 逻辑回归 3.1 Motivatio…