目录
1.Linux下进程和线程
进程
进程是系统分配资源的基本单位。每个进程都拥有独立的地址空间、内存、数据栈等系统资源
进程之间通过IPC(进程间通信)机制进行通信和数据共享
进程是程序的一次执行过程,它包含了程序代码、数据和系统资源(如打开的文件、网络连接等)
在Linux中,进程由进程ID(PID)唯一标识
线程
线程是CPU调度的基本单位。线程共享进程的资源,包括代码段、数据段、堆等,但每个线程拥有自己的栈和程序计数器
线程之间共享进程的资源,这使得线程间的通信和数据共享变得相对简单
线程的主要优点是并发执行和共享资源,可以充分利用多核CPU的并行处理能力
在Linux中,线程通常是通过pthread库来创建和管理的
区别
进程 | 线程 | |
---|---|---|
资源占用 | 拥有独立的资源空间 | 共享进程的资源 |
并发性 | 多个线程可以在同一个进程中并发执行 | 多个进程则需要通过IPC进行通信和协调 |
独立性 | 进程之间是完全独立的,一个进程的崩溃不会影响其他进程 | 线程共享进程的资源,一个线程的崩溃可能导致整个进程的崩溃 |
通信方式 | 通常需要使用系统调用或网络等机制 | 可以通过共享内存或全局变量等方式实现 |
查看进程pid
命令:
ps -a
终止进程pid
命令:
kill <PID>
2.Linux虚拟内存管理与stm32内存映射
设计目标与架构差异
Linux虚拟内存管理 | STM32物理内存映射 | 技术原理与引用来源 | |
---|---|---|---|
目标 | 实现多任务隔离、动态内存扩展及安全性,支持进程独立地址空间和内存超量分配(交换空间) | 为实时嵌入式系统提供确定性响应,直接控制硬件资源(如外设寄存器、SRAM) | Linux面向通用计算场景,需处理复杂调度;STM32面向实时控制,资源固定且无需动态扩展 |
硬件依赖 | 必须依赖MMU(内存管理单元)实现虚拟地址转换 | 无MMU支持,直接通过总线访问物理地址 | MMU通过页表实现虚拟→物理地址映射;STM32的Cortex-M系列CPU直接操作物理地址 |
地址空间管理机制对比
Linux虚拟内存管理 | STM32物理内存映射 | |
---|---|---|
地址转换 | 通过多级页表(如四级页表)和TLB缓存完成虚拟→物理地址转换 | 无地址转换,外设/存储器通过固定地址直接访问(如Flash起始于0x08000000,SRAM起始于0x20000000) |
内存分配 | 动态分页:按需分配物理内存(如malloc 触发缺页中断),支持交换空间(Swap)扩展可用内存 | 静态预分配:芯片设计时固定地址映射,无法动态扩展或重新分配 |
资源隔离 | 进程间完全隔离,通过页表权限位(读/写/执行)保护内存 | 无隔离机制,所有代码共享同一物理地址空间 |
内存使用与性能特性
Linux虚拟内存管理 | STM32物理内存映射 | |
---|---|---|
访问延迟 | 存在地址转换开销(TLB未命中时需查页表),但可通过预取和缓存优化 | 零转换延迟,直接访问物理地址(适用于实时控制) |
内存共享 | 支持进程间共享内存(如mmap 映射共享库),需同步机制 | 通过全局变量或硬件寄存器直接共享数据,无同步层但需开发者保证原子性 |
错误处理 | 缺页中断(Page Fault)自动加载数据到物理内存,崩溃仅影响当前进程 | 非法地址访问直接导致硬件错误(HardFault),可能引发系统复位 |
3.Linux系统调用函数
fork()
功能:复制当前进程(父进程),生成一个几乎完全相同的子进程。
执行流程:
-
父进程调用
fork()
后,内核创建子进程。 -
子进程继承父进程的代码段、数据段、堆栈、文件描述符等资源。
-
父子进程从
fork()
返回后开始并发执行,但拥有独立的地址空间。在 Linux 系统下使用 CMake 编译调用
fork
函数程序的完整步骤,结合*CMake 配置和 系统调用实现,具体流程如下:1.创建一个
main.c
文件,内容如下:#include <stdio.h> #include <unistd.h> // 包含 fork 系统调用的头文件 int main() { pid_t pid = fork(); // 创建子进程 if (pid < 0) { perror("fork 失败"); return 1; } else if (pid == 0) { // 子进程代码 printf("子进程 PID = %d (父进程 PID = %d)\n", getpid(), getppid()); } else { // 父进程代码 printf("父进程 PID = %d (子进程 PID = %d)\n", getpid(), pid); } return 0; }
2.在项目根目录下创建
CMakeLists.txt
,内容如下:cmake_minimum_required(VERSION 3.10) # 最低 CMake 版本要求 project(ForkDemo) # 项目名称 # 添加可执行文件 add_executable(fork_demo main.c) # 设置 C 标准(可选) set(CMAKE_C_STANDARD 11)
3.构建与编译
创建构建目录并生成 Makefile:
mkdir build && cd build cmake .. # 生成 Makefile 和其他构建文件
编译程序:
make # 生成可执行文件 fork_demo
执行:
./fork_demo
结果如下:
wait()
-
功能:父进程阻塞等待任意子进程终止,并回收其资源(避免僵尸进程)。
-
补充:
waitpid()
可指定等待特定子进程或非阻塞模式1.创建一个
fork_wait.c
文件,内容如下:#include <stdio.h> #include <unistd.h> #include <sys/wait.h> // 必须包含此头文件以使用 wait() int main() { pid_t pid = fork(); if (pid < 0) { perror("fork 失败"); return 1; } else if (pid == 0) { // 子进程执行任务 printf("子进程 PID = %d\n", getpid()); sleep(2); // 模拟耗时操作 printf("子进程结束\n"); } else { // 父进程等待子进程结束 printf("父进程 PID = %d,等待子进程 %d...\n", getpid(), pid); int status; wait(&status); // 阻塞等待子进程结束 printf("子进程退出状态: %d\n", WEXITSTATUS(status)); } return 0; }
2.在相同目录下创建
Makefile
,内容如下:# 定义编译器和编译选项 CC = gcc CFLAGS = -Wall -Wextra -std=c11 # 定义目标可执行文件名和源文件 TARGET = fork_wait_demo SRC = fork_wait.c # 默认目标 all: $(TARGET) # 编译规则 $(TARGET): $(SRC) $(CC) $(CFLAGS) -o $@ $^ # 清理生成的文件 clean: rm -f $(TARGET) # 伪目标声明(避免与同名文件冲突) .PHONY: all clean
3.编译与运行
编译程序:
make # 自动执行 Makefile 中的 all 目标
运行程序:
./fork_wait_demo
exec()
功能:替换当前进程的代码段,加载并执行一个新程序。
-
调用成功后,原进程的代码被新程序覆盖,但PID不变。
-
若失败,返回
-1
并保留原进程继续执行。gcc 命令行编译
1.创建一个名为
fork_exec.c
的文件,并将代码写入:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程执行 ls -l
execl("/bin/ls", "ls", "-l", NULL);
perror("exec failed"); // 若 exec 失败才会执行
return 1;
}
return 0;
}
2.使用 GCC 编译代码,生成可执行文件 fork_demo
gcc -o fork_demo fork_exec.c
3.执行生成的可执行文件:
./fork_demo
4.树莓派环境下练习
创建账号
通过putty连接
ssh pi@<ip地址>
比如我的是:ssh pi@192.168.12.250
1创建用户账号
使用adduser
命令创建用户
sudo adduser username # 替换username为组员用户名
-
执行后会提示设置密码及用户信息(非必填项可直接回车跳过)
-
默认自动生成同名主目录
/home/username
2.配置用户权限
-
加入必要用户组(如
sudo
组赋予管理员权限)sudo usermod -aG sudo username # 将用户加入sudo组 sudo usermod -aG adm,dialout,plugdev username # 加入常用硬件访问组
-
验证用户权限:
id username # 查看用户所属组[6](@ref) groups username # 列出用户所有附加组
3.查看用户
通过 compgen
命令直接生成系统已知的用户名列表:
compgen -u
登录自己的账号进行练习
1. 安装GCC编译器
在树莓派Ubuntu系统中,默认可能未安装GCC。需通过以下命令安装:
sudo apt update
sudo apt install build-essential
此命令会安装GCC及编译所需的工具链(如make
、g++
等)
2. 编写包含fork()
的C程序
在home
目录下创建文件(例如fork_demo.c
),编写代码:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork失败\n");
return 1;
} else if (pid == 0) {
printf("子进程ID: %d\n", getpid());
} else {
printf("父进程ID: %d,子进程ID: %d\n", getpid(), pid);
}
return 0;
}
3. 使用GCC编译
gcc -o fork_demo fork_demo.c
4. 运行程序
./fork_demo
参考链接: