Linux线程与进程的栈管理、页表机制及线程封装

目录

一、线程栈管理

进程(主线程)栈管理

子线程栈管理

二、页表和页表项

页表标志位定义

关键数据结构

页表分配函数

三、通用线程封装实现

实现要点

四、直接调用clone系统调用

关键点说明


一、线程栈管理

虽然Linux内核将线程和进程统一在task_struct结构中管理,但在地址空间的栈(stack)管理上仍有显著区别:

进程(主线程)栈管理

  • 主线程栈 :可以简单理解为main()函数的栈空间

  • 创建机制 :在fork()时复制父进程的stack空间地址,采用写时拷贝(COW)机制

  • 动态增长:栈空间可以动态增长,但有上限限制

  • 溢出处理

    • 超出扩充上限时会触发栈溢出,内核会发送段错误信号(SIGSEGV)给进程

    • 进程栈是唯一可以访问未映射页而不一定会立即发生段错误的情况------只有超出扩充上限才会报错

子线程栈管理

  • 创建方式 :通常通过glibc/uclibc的pthread_create()接口创建

  • 内存分配

    • 使用mmap系统调用在文件映射区(共享区)分配固定大小的栈空间

    • 关键代码(来自glibc的nptl/allocatestack.c):

      cpp 复制代码
      mem = mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
    • 默认栈大小通常为8MB(可通过pthread_attr_setstacksize()调整)

  • 与进程栈的区别

    • 栈空间是预先固定分配的,不能动态增长

    • 用尽栈空间会导致未定义行为(通常是崩溃),而不会像进程栈那样触发动态增长

  • 系统调用流程 :glibc通过mmap获取栈内存后,调用sys_clone:

    cpp 复制代码
    int sys_clone(struct pt_regs *regs) {
        unsigned long clone_flags;
        unsigned long newsp;
        // ...
        clone_flags = regs->bx;
        newsp = regs->cx; // 获取mmap得到的线程栈指针
        // ...
        return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);
    }
  • 内存访问特性

    • 线程栈在进程地址空间中通过mmap映射的私有内存区域

    • 理论上线程私有,但同一进程的线程会浅拷贝生成者的task_struct字段,其他线程可能访问到


二、页表和页表项

Linux内核采用两级页表结构,维护硬件页表和Linux内部页表两套体系:

页表标志位定义

cpp 复制代码
/* 页表标志位 */
#define L_PTE_PRESENT   (1 << 0)   // 页表项存在标志
#define L_PTE_FILE      (1 << 1)   // 仅当!PRESENT时使用,表示非内存映射文件
#define L_PTE_YOUNG     (1 << 1)   // 页被访问过(用于页面置换算法)
#define L_PTE_BUFFERABLE (1 << 2)  // 可缓冲
#define L_PTE_CACHEABLE (1 << 3)   // 可缓存
#define L_PTE_USER      (1 << 4)   // 用户可访问
#define L_PTE_WRITE     (1 << 5)   // 可写
#define L_PTE_EXEC      (1 << 6)   // 可执行
#define L_PTE_DIRTY     (1 << 7)   // 页被修改过
#define L_PTE_COHERENT  (1 << 9)   // I/O一致性(xsc3)
#define L_PTE_SHARED    (1 << 10)  // CPU间共享(v6)
#define L_PTE_ASID      (1 << 11)  // 非全局(使用ASID, v6)

关键数据结构

cpp 复制代码
typedef struct { unsigned long pte; } pte_t;  // 页表项
typedef struct { unsigned long pgd; } pgd_t;  // 页全局目录项

struct mm_struct {
    struct vm_area_struct *mmap;        // 虚拟内存区域链表
    struct rb_root mm_rb;               // VMA红黑树根节点
    unsigned long mmap_base;            // mmap区域基址
    unsigned long task_size;            // 任务虚拟内存空间大小
    pgd_t *pgd;                         // 页目录起始地址
    // ...
};

页表分配函数

页全局目录分配

cpp 复制代码
pgd_t *pgd_alloc(struct mm_struct *mm) {
    pgd_t *ret = (pgd_t *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
    // 初始化页表项...
    return ret;
}

页表项分配

cpp 复制代码
pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) {
    pte_t *pte = (pte_t *)__get_free_page(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO);
    return pte;
}

三、通用线程封装实现

下面是一个支持任意参数传递的线程封装类实现:

cpp 复制代码
#include <iostream>
#include <functional>
#include <memory>
#include <pthread.h>
#include <unistd.h>

class Thread {
public:
    Thread() : thread_id_(0), running_(false) {}
    
    ~Thread() {
        if (running_) {
            pthread_detach(thread_id_);
        }
    }

    template <typename Callable, typename... Args>
    bool start(Callable&& func, Args&&... args) {
        if (running_) {
            std::cerr << "Thread is already running!" << std::endl;
            return false;
        }

        // 使用完美转发和std::bind打包任务
        auto task = std::make_shared<std::function<void()>>(
            std::bind(std::forward<Callable>(func), std::forward<Args>(args)...)
        );

        // 创建线程,传递任务指针
        auto* task_ptr = new std::shared_ptr<std::function<void()>>(task);
        if (pthread_create(&thread_id_, nullptr, &Thread::threadEntry, task_ptr) != 0) {
            delete task_ptr; // 失败时清理
            std::cerr << "Failed to create thread!" << std::endl;
            return false;
        }

        running_ = true;
        return true;
    }

    void join() {
        if (running_) {
            pthread_join(thread_id_, nullptr);
            running_ = false;
        }
    }

private:
    pthread_t thread_id_;
    bool running_;

    static void* threadEntry(void* arg) {
        // 使用unique_ptr自动管理资源
        std::unique_ptr<std::shared_ptr<std::function<void()>>> task_ptr(
            static_cast<std::shared_ptr<std::function<void()>>*>(arg)
        );
        
        // 执行任务
        (*(*task_ptr))();
        
        return nullptr;
    }
};

// 示例函数
void printMessage(const std::string& message, int value, int a, int b, int c) {
    std::cout << "Message: " << message << ", Value: " << value << std::endl;
    std::cout << "a:" << a << std::endl;
    std::cout << "b:" << b << std::endl;
    std::cout << "c:" << c << std::endl;
    sleep(10);
}

int main() {
    Thread thread;
    thread.start(printMessage, "Hello, World!", 42, 1, 2, 3);
    thread.join();
    return 0;
}

实现要点

  1. 完美转发 :使用std::forward保持参数的左值/右值属性

  2. 任务打包 :将可调用对象和参数打包为std::function<void()>

  3. 内存管理

    • 使用shared_ptr确保任务对象在线程执行期间有效

    • 使用unique_ptr在线程入口函数中自动释放资源

  4. 线程安全:正确处理线程创建失败时的资源释放


四、直接调用clone系统调用

下面是一个直接使用clone()系统调用的示例:

cpp 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024) // 1MB栈空间

static int child_func(void *arg) {
    printf("Child process: PID = %d\n", getpid());
    return 0;
}

int main() {
    // 分配子进程栈空间
    char *stack = (char*)malloc(STACK_SIZE);
    if (!stack) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    // 使用clone创建子进程
    // CLONE_VM: 共享虚拟内存空间
    // SIGCHLD: 子进程退出时发送SIGCHLD信号
    pid_t pid = clone(child_func, stack + STACK_SIZE, CLONE_VM | SIGCHLD, NULL);
    if (pid == -1) {
        perror("clone");
        free(stack);
        exit(EXIT_FAILURE);
    }

    printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);

    // 等待子进程结束
    if (waitpid(pid, NULL, 0) == -1) {
        perror("waitpid");
        free(stack);
        exit(EXIT_FAILURE);
    }

    free(stack);
    return 0;
}

关键点说明

  1. 栈分配

    • 必须手动分配栈空间

    • 栈指针需要指向分配空间的末尾(因为栈向下增长)

  2. clone标志

    • CLONE_VM: 共享虚拟内存空间

    • CLONE_FS/CLONE_FILES: 共享文件系统信息/文件描述符

    • CLONE_SIGHAND: 共享信号处理程序

    • SIGCHLD: 子进程退出时发送SIGCHLD信号

  3. 资源管理

    • 必须手动管理栈内存的分配和释放

    • 需要正确处理错误情况下的资源释放

  4. 与pthread_create的区别

    • 更底层,提供更多控制选项

    • 需要手动处理更多细节(如栈管理)

    • 通常用于实现线程库或特殊需求的进程创建

这个示例展示了Linux下线程/进程创建的底层机制,与高层pthread接口形成对比,有助于深入理解Linux的多任务处理机制。

相关推荐
立志成为大牛的小牛4 小时前
数据结构——三十一、最小生成树(王道408)
数据结构·学习·程序人生·考研·算法
JMzz4 小时前
Rust 中的数据结构选择与性能影响:从算法复杂度到硬件特性 [特殊字符]
开发语言·数据结构·后端·算法·性能优化·rust
星空露珠6 小时前
数独生成题目lua脚本
数据结构·数据库·算法·游戏·lua
给大佬递杯卡布奇诺6 小时前
FFmpeg 基本数据结构 URLContext分析
数据结构·c++·ffmpeg·音视频
极客智造15 小时前
线性数据结构深度解析:数组、链表、栈与队列的实现与应用
数据结构·链表
Zhu_S W17 小时前
Redis跳表:高效有序数据结构的深度剖析
数据结构·数据库·redis
是那盏灯塔17 小时前
【算法】——动态规划之01背包问题
数据结构·c++·算法·动态规划
jinmo_C++17 小时前
数据结构_深入理解堆(大根堆 小根堆)与优先队列:从理论到手撕实现
java·数据结构·算法
Excuse_lighttime18 小时前
排序数组(快速排序算法)
java·数据结构·算法·leetcode·eclipse·排序算法