Linux系统调用编程

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
    

    结果如下:

image-20250401190019560

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
    

    image-20250402212752673

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

image-20250402161710914

4.树莓派环境下练习

创建账号

通过putty连接

ssh pi@<ip地址>
比如我的是:ssh pi@192.168.12.250

image-20250404162319023

1创建用户账号

使用adduser命令创建用户

sudo adduser username  # 替换username为组员用户名
  • 执行后会提示设置密码及用户信息(非必填项可直接回车跳过)

  • 默认自动生成同名主目录 /home/username

    image-20250404163559761

2.配置用户权限
  1. 加入必要用户组(如sudo组赋予管理员权限)

    sudo usermod -aG sudo username  # 将用户加入sudo组
    sudo usermod -aG adm,dialout,plugdev  username  # 加入常用硬件访问组
    
  2. 验证用户权限

    id username  # 查看用户所属组[6](@ref)
    groups username  # 列出用户所有附加组
    

image-20250404163902808

3.查看用户

通过 compgen 命令直接生成系统已知的用户名列表:

compgen -u

image-20250404164722028

登录自己的账号进行练习

1. 安装GCC编译器

在树莓派Ubuntu系统中,默认可能未安装GCC。需通过以下命令安装:

sudo apt update
sudo apt install build-essential

此命令会安装GCC及编译所需的工具链(如makeg++等)

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

image-20250404171426775

参考链接:

Linux下进程和线程

Linux 内存管理 详解(虚拟内存、物理内存,进程地址空间)

Linux内存管理:深度解析与探索

一起玩儿树莓派——07 用户的创建、删除及其他相关命令

putty的复制和粘贴

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值