【Linux笔记】线程概念与控制

手把手教你用STM32单片机DIY一个简易PLC(附完整电路图与PCB文件) 本文详细介绍了如何利用STM32单片机DIY一个简易PLC控制器,涵盖硬件设计、电路图解析、PCB布局优化及固件框架实现。通过完整的电路图和PCB文件,帮助读者从元器件选型到工业级优化,逐步构建可靠的PLC系统,特别适合工业自动化爱好者和学习者实践。 阅读详情

一、线程概念

1.1 理解线程

通俗理解:线程是进程中的一个执行分支 (执行流),CPU 实际在跑的那条指令序列

本质理解:Linux 内核中进程和线程使用同一个数据结构 task_struct 来表示,所以线程本质上就是共享部分资源的"轻量级进程"

打个比方:

  • 进程像一家工厂,拥有厂房、设备、原料 (内存、文件等资源)

  • 线程像工厂里的工人,每个工人各自干活,共享工厂的一切资源

  • 工厂至少要有一个工人,所以一个进程至少有一个线程 (主线程)

  • 多招几个工人(线程),他们共用厂房原料,沟通成本极低,但一个工人操作失误弄坏原料(写坏共享内存),全厂遭殃。

1.2 进程与线程共享的资源

从内核和资源的角度:在Linux中,进程是资源分配的单位(负责管理内存和文件),线程是调度的单位(负责占用CPU)。

"线程是轻量级进程"——站在Linux内核视角里根本没有进程和线程两种东西,只有一群 task_struct,谁和谁共享同一套资源对象,谁和谁就是"同一个进程里的线程"。

在Linux下“资源”具体指以下三大类
​
1. 内存资源(地址空间)
​
- 进程独有:每个进程都有一张独立的页表(Page Table),映射到不同的物理内存。
​
- 线程共享:同一个进程里的线程,共用同一张页表。(因此,线程A malloc 出一块内存,线程B 直接能用指针访问)
​
​
2.I/O 资源(文件描述符表)
​
- 进程独有:每个进程维护一张打开文件描述符表,记录着当前打开了哪些文件、socket(网络套接字)、管道。
​
- 线程共享:线程间共享这张表。( 所以线程A打开 fd= 3,线程B可以直接往 fd=3 写数据)
​
​
3. 身份与状态资源(上下文)
​
- 进程ID(PID/PPID)、用户ID(UID/GID)、工作组(Session ID)。
​
- 信号处置表(Signal Handlers):比如你对 SIGINT 设置了自定义回调函数。
​
  线程共享这张表,但对于信号的状态表(Pending)线程是独立的。
​
- 当前工作目录(cwd)和环境变量(environ)。

线程共享进程资源的核心:资源被做成独立的内核对象,线程通过"指针"指向它们,而不是把资源复制一份。

多个线程的 task_struct 里,指向资源的指针,通过引用计数的方式指向同一个对象。

  • 共享的(资源):mm、files、sighand ...指向同一对象

  • 私有的(执行上下文):寄存器、程序计数器、用户栈、内核栈、TLS、errno、TID、调度状态……每个线程一份

图示理解线程资源的指向:

线程A的 task_struct 和 线程B的 task_struct
 |
 ├─ mm ────────┐           
 │             └─────→  同一个 mm_struct(引用计数 = 2) (同一份页表)
 │                      
 ├─ files ─────┐          
 │             └─────→  同一个 files_struct(引用计数 = 2) (同一张文件描述符表)
 │                      
 └─ sighand ───┐          
               └─────→  同一个 sighand_struct(引用计数 = 2) (同一张信号处理表)           

1.3 线程中私有的资源

1.3.1 CPU 寄存器 ---硬件上下文

当线程在运行时:这些硬件上下文数据放在 CPU 中;

当线程被切换时:内核把它们保存到该线程的内核栈(用PCB进行维护) 中,切回来时进行恢复。

- 程序计数器 PC:我执行到哪条指令了
​
- 栈指针 SP:指向我的用户栈
​
- 通用寄存器:计算中间的结果
​
- 标志寄存器、浮点/向量寄存器

1.3.2 虚拟内存 ---栈

- 用户栈:局部变量、函数参数、返回地址全在这里,局部变量。  
  
  ·对于主线程的用户栈,位于虚拟地址空间的栈区,能够动态增长,在可执行程序加载时进行分配。
  ·对于子线程的用户栈,位于虚拟地址空间的共享区,大小固定,由mmap进行申请
  ·用户栈通过task_struct中的mm_struct结构体和vm_area_struct进行管理。
  
- 内核栈:线程通过系统调用进入内核时,走自己独立的内核栈。 
  
  ·在内核空间中都有的固定大小的栈。
  ·内核栈通过task_struct中的stack指针进行管理
​
- TLS(线程局部存储): 通过__thread/ thread_local 修饰的变量 和 errno 变量。
​
   ·每个线程的 fs 寄存器指向自己那一块 TLS,所以同一行代码里写 errno,各线程读写的是各自的副本。

1.3.3 task_struct ---部分字段

- LWP :线程身份ID
​
- 调度状态(运行/睡眠)、优先级、nice 值、CPU 亲和性、调度策略
​
- 信号屏蔽字 blocked:线程选择挡住哪些信号
​
- 线程私有的 pending 信号

1.3.4 总结

线程拥有独立的:
​
1. 寄存器
​
2. 程序计数器 PC
​
3. 线程栈 Stack
​
4. 线程局部存储 TLS
​
5. 调度状态

线程共享进程的:
​
1. 代码段
​
2. 全局变量
​
3. 静态变量
​
4. 堆 Heap
​
5. 文件描述符
​
6. Socket
​
7. 进程资源

二、Linux 线程控制

2.2 pthread_create - 创建线程

2.2.1 基础使用要求

1.头文件包含: #include <pthread.h>
​
2.函数原型:
​
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
​
3.函数功能:创建出一个新的线程,产生一个新的执行流。

注:

A. 编译链接参数:pthread 不是 Linux 默认链接的库,编译时需要显式添加 -lpthread 或 -pthread 参数,否则会报 undefined reference to pthread_create 的链接错误。

B. 错误处理规则:与常规系统调用不同,pthread_create 失败时不会设置全局 errno,而是直接返回对应的错误码,需要用strerror(ret) 将错误码转换为可读的错误信息,不能使用 perror()。

2.2.1 参数说明

1.参数一:pthread_t *thread

- 指向 pthread_t 类型变量的指针,用于接收新创建线程的唯一标识符(线程 ID)。

- 后续对该线程的所有操作(如等待结束、分离、取消等)都需要通过这个 ID 进行。


2.参数二:const pthread_attr_t *attr (一般可忽略)

- 栈大小:默认在 x86_64 架构下为 2MB,可通过 pthread_attr_setstacksize() 修改

- 分离状态:默认可连接(joinable),需要调用 pthread_join() 回收资源;也可设置为分离(detached),线程结束后自动释放资源

- 调度策略:如 SCHED_FIFO(实时先进先出)、SCHED_RR(实时轮转)等,默认为普通分时调度 SCHED_OTHER


3.参数三:start_routine(线程入口函数)

函数头必须按照该格式设计:void* 函数名 (void * arg) ,线程执行的入口部分。 


4.参数四: void *arg (传参给线程入口函数)

- 传递给 start_routine 的唯一参数,类型为 void*。

- 如果需要传递多个参数,必须将所有参数封装到结构体中,传递结构体的地址。

2.2.2 函数返回值

成功:返回 0,此时 thread 指向的变量会被设置为新线程的 ID。

失败:返回非零的错误码,thread 指向的内容未定义,不会创建新线程。

注:需要通过strerror进行输出错误信息。

函数的错误码:

错误码含义
EAGAIN系统资源不足,无法创建新线程(如超过线程数上限、内存不足等)
EINVALattr 属性配置无效,或 start_routine 为 NULL
EPERM没有权限设置 attr 中指定的调度策略或参数(如实时调度需要 root 权限)

2.2.3 代码演示

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void *thread_run(void *args)
{
    int cnt = 5;
    while (cnt--)
    {
        printf("%s starts running...\n",name);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid, nullptr, thread_run, (void *)"thread -1");

    if (ret != 0)
    {
        strerror(ret);
    }

    int cnt = 5;
    while (cnt--)
    {
        printf("Main thread running...\n");
    }
    
    return 0;
}

运行结果如下所示:

Main thread running...
Main thread running...
Main thread running...
Main thread running...
Main thread running...
thread -1 starts running...
thread -1 starts running...
thread -1 starts running...
thread -1 starts running...
thread -1 starts running...
thread -1 starts running...

2.3 pthread_join -线程等待

2.3.1 基础使用条件

1.头文件包含 #include <pthread.h>

2.函数原型:int pthread_join(pthread_t thread, void **retval);

3.函数功能:同步和回收可连接(joinable)的线程资源

2.3.2 参数说明

1.参数一:pthread_t thread

- 目标线程的 ID,即 pthread_create 第一个输出参数获得的值。

- 必须是当前进程中有效的、尚未被 join 且未分离的线程 ID。


2.参数二:void **retval

- 指向 void* 指针的二级指针,用于接收目标线程通过 return 或 pthread_exit() 传递的返回值。

- 如果不需要获取返回值,传 NULL 即可。

- 如果目标线程是被 pthread_cancel() 取消的,*retval 会被设置为特殊常量 PTHREAD_CANCELED。

注意:绝对不要将局部变量的地址作为返回值传出!因为线程结束后其栈帧已被销毁,该地址变为悬空指针,访问它将导致未定义行为,应使用堆内存、全局变量或静态变量来传递复杂数据。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

void* worker(void* arg) 
{
    int input = *(int*)arg;
    int* result = malloc(sizeof(int));  // 堆上分配,安全
    *result = input * input;
    return result;
}

int main() 
{
    pthread_t tid;
    int val = 7;

    pthread_create(&tid, NULL, worker, &val);

    void* ret;
    int rc = pthread_join(tid, &ret);
    if (rc != 0) 
    {
        printf("join failed: %s\n", strerror(rc));
        return 1;
    }

    printf("7^2 = %d\n", *(int*)ret);
    free(ret);  // 记得释放堆内存
    return 0;
}

2.4 pthread_detach -线程分离

2.4.1 基础使用条件

1.头文件包含: #include <pthread.h>

2.函数原型:int pthread_detach(pthread_t thread);

3.函数功能:将指定线程标记为"分离状态(detached)",分离线程终止时,系统自动回收其资源(栈、线程描述符等),无需也不允许其他线程调用 pthread_join。

2.4.2 detach后的线程状态

操作系统detach线程的执行过程

1. 立即释放线程栈内存(通常几 MB)

2. 释放线程控制块(TCB)

3. 丢弃返回值(即使线程函数 return 了非 NULL 值,也无处接收)

4. 不保留任何可供 join 的状态

2.4.3 detach的两种时机

A. 运行时 detach

pthread_t tid;
pthread_create(&tid, NULL, worker, arg);
pthread_detach(tid);  // 创建后分离

B. 创建前 detach

pthread_attr_t attr;//线程属性
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_t tid;
pthread_create(&tid, &attr, worker, arg);  // 出生即分离

pthread_attr_destroy(&attr);  //  属性对象用完必须销毁,否则泄漏

C. detach 时间线分析

方式A的时间线:

  create ──────────────── detach
         	↑ 危险窗口 ↑
         线程可能在此区间内已经终止!


如果线程在 pthread_create 和 pthread_detach 之间就执行完毕并退出了,而系统又恰好立即回收了其资源,那pthread_detach(tid) 操作的就是一个无效的线程标识符,属于未定义行为。

方式B的时间线:

  create(DETACHED) ───→ 线程从第一刻起就是分离状态 不存在竞态窗口

2.4.4 detach 的约束

A. 绝不能对分离线程调用 join

pthread_detach(tid);
pthread_join(tid, NULL);  //  明确规定这是未定义行为

B. 不能获取返回值

void* worker(void* arg) 
{
    return some_result;  //  如果线程是 detached,这个返回值被丢弃
}

C. 参数生命周期必须自给自足

// 经典错误

void * worker(void * args)
{
	//...执行任务
	return NULL;
}

void spawn_task(int id) 
{
    int local_id = id;
    pthread_create(&tid, &attr, worker, &local_id);  // 传栈变量地址
    //  spawn_task 返回后 local_id 销毁
    //  分离线程可能还在运行,此时读到悬空指针
}

在 pthread_join模式下,主线程通过等待隐式延长了参数的生命周期。

detach 模式下没有这个安全机制,spawn_task函数结束后,局部变量销毁,导致线程中的指针悬空。

2.5 线程终止

线程终止:是线程生命周期的最后一个环节,也是资源回收的前置条件。

POSIX 提供了三种终止方式,它们的语义、副作用和适用场景各不相同。

2.5.1 线程执行 return

基本语义:线程入口函数void*(*)(void*)执行 return 语句,是最自然的终止方式。

void* worker(void* arg) 
{
    int id = *(int*)arg;
    printf("Thread %d doing work...\n", id);

    // 正常执行完毕,return 一个值
    return (void*)(long)id;  // 将整数转为 void* 返回
}

关键细节

细节一:return 的值就是 pthread_join 能取到的值

void* worker(void* arg) 
{
    int id = *(int*)arg;
    printf("Thread %d doing work...\n", id);

    // 正常执行完毕,return 一个值
    return (void*)(long)id;  // 将整数转为 void* 返回
}

void* retval;
pthread_join(tid, &retval); //二级指针进行获取
printf("Thread returned: %ld\n", (long)retval);

细节 2:return NULL 表示"无返回值",不是"失败"

void* worker(void* arg) 
{
    // 做一些事情...
    return NULL;  // 正常终止,没有需要传递的结果
}

NULL 和 PTHREAD_CANCELED 是不同的,join 可以通过这个区分判断线程是"自然死亡"还是"被杀死"。

  • NULL 表示线程自行决定正常结束;

  • PTHREAD_CANCELED 是系统标记,表示线程被外部取消。

细节 3:绝不能返回局部变量的地址

//错误做法:
void* worker(void* arg) 
{
    char buf[128];
    snprintf(buf, sizeof(buf), "result_%d", *(int*)arg);
    return buf;  //  致命错误!buf 在 return 后栈帧销毁
}


//正确做法:

// 方案A:堆分配
char* buf = malloc(128);
snprintf(buf, 128, "result_%d", *(int*)arg);
return buf;  //  调用方负责 free

// 方案B:静态/全局变量(不推荐,非线程安全)
static char buf[128];  // 多线程同时 return 会覆盖

// 方案C:返回整数值(最安全)
return (void*)(long)some_int;

细节 4:return 只终止当前线程,不影响其他线程

void* worker(void* arg) 
{
    return NULL;  // 仅本线程结束
    // main 线程和其他子线程继续运行
}

细节 5:C++ 中 return 会触发栈上对象的析构

void* worker(void* arg) 
{
    std::string s = "hello";
    std::lock_guard<std::mutex> lock(mtx);
    // ... 做一些事情 ...
    return NULL;
    //  s 的析构函数被调用
    //  lock 的析构函数被调用(mutex 被解锁)
    //  所有 RAII 对象正常清理
}

2.5.2 线程执行 pthread_exit

函数原型:

头文件包含:#include <pthread.h>

函数原型:void pthread_exit(void *retval);  

核心用法:显式终止调用它的线程。

retval 的含义与 return 完全一致——都是传递给 pthread_join 的返回值。

void* worker(void* arg) 
{
    int id = *(int*)arg;

    if (some_error_condition) {
        fprintf(stderr, "Thread %d encountered error, exiting early.\n", id);
        pthread_exit((void*)-1);  // 提前终止,不执行后续代码
    }

    // 正常逻辑...
    return (void*)(long)id;
}

细节分析:

POSIX 标准的规定:pthread_exit 应当触发 C++ 栈展开(stack unwinding),即调用析构函数。但实际行为依赖编译器和平台:

  • Linux glibc + g++:通常正确触发析构(通过内部 __cleanup 机制)

  • 某些嵌入式/旧编译器:可能直接 longjmp,跳过析构。

  • C++ 标准本身:不承认 pthread_exit 的存在,行为是实现定义的。

void* worker(void* arg) 
{
    std::string s = "important data";
    std::lock_guard<std::mutex> lock(mtx);

    helper_function();  // 内部调用了 pthread_exit

    //  问题:s 和 lock 的析构函数是否被调用?
}

void helper_function()
{
    // ... 一些逻辑 ...
    pthread_exit(NULL);  // 从辅助函数中终止线程
}

2.6 pthread库

2.6.1 线程库的本质定位

线程库的本质:Linux 内核本身没有"线程"这个概念,线程是用户态库(pthread)对内核"轻量级进程"机制的封装。

内核提供"底层结构"(轻量级进程task_struct + futex),pthread 库负责"封装"成标准化的线程接口。

			
┌─────────────────────────────────┐
│           pthread 	       	  │ 用户空间:
│  ┌──────────┐  ┌──────────┐     │
│  │ struct   │  │ struct   │     │  ← 线程控制块(TCB):管理线程属性、栈地址等
│  │ pthread  │  │ pthread  │     │     
│  │ (TCB)    │  │ (TCB)    │     │
│  └────┬─────┘  └────┬─────┘     │
│       │ 线程栈       │ 线程栈     │
│       ▼             ▼           │
├───────┼─────────────┼───────────┤
│       │ clone()     │ clone()   │  ← 系统调用边界
├───────┼─────────────┼───────────┤
│       ▼             ▼           │  内核空间:   
│  ┌───────────┐ ┌────────────┐   │
│  │task_struct│ │task_struct │   │  ← 内核调度实体(LWP):cpu的调度单位
│  │  (LWP)    │ │   (LWP)    │   │     
│  └───────────┘ └────────────┘   │
│                                 │
└─────────────────────────────────┘

2.6.2 线程地址空间布局

线程地址空间布局图:

┌─────────────────────┐  Highest addresses
│      				  │
|	Kernel space 	  |
|					  |
┌─────────────────────┐
|      Stack		  |
|    				  |
│  Main thread stack  │  ← 主线程栈
├─────────────────────┤
│   mmap shared area  │  ← TCBs + thread stacks for other threads are here
│  ┌───────────────┐  │    其他线程的 TCB + 线程栈都在这里
│  │ TCB (Thread 2)│  │
│  │ Stack of Thd 2│  │
│  ├───────────────┤  │
│  │ TCB (Thread 3)│  │
│  │ Stack of Thd 3│  │
│  └───────────────┘  │
|       ...			  │
├─────────────────────┤
│   Heap (shared)     │  ←堆区
├─────────────────────┤
│   Data segment 	  │  ←数据段
├─────────────────────┤
|   Code segment      |  ←代码段
└─────────────────────┘  Low addresses 低地址

image-20260810211003725

2.6.3 内核空间管理线程

Linux 内核对进程和线程都用 task_struct 表示,每个线程在内核中都有一个独立的 task_struct,这就是一个轻量级进(LWP)。

task_struct 中的关键字段:

struct task_struct
{
    //线程的ID
    pid_t pid;              // 内核线程 ID(每个 LWP 唯一)
    pid_t tgid;             // 线程组 ID(同进程所有线程共享)
    
    // 管理线程
    struct task_struct *group_leader;  // 指向主线程
    struct list_head thread_group;     // 线程组链表
    struct mm_struct *mm;   // 地址空间描述符(同组线程共享)
    
    // 线程的上下文
    // ... 寄存器、调度优先级、状态等  
};

  • pid:内核视角的"进程 ID",对 LWP 来说就是线程 ID(通过 gettid() 获取)

  • tgid:线程组 ID,等于主线程的 pid(getpid() 返回的就是 tgid)

  • mm:指向虚拟内存描述符,同组线程共享同一个 mm,因此共享地址空间

2.6.4 用户空间管理线程

┌──────────────────────────────┐  高地址
│    struct pthread(TCB)      │  ← pthread_t 就是这个结构体的首地址
├──────────────────────────────┤
│    线程局部存储(TLS)          │  ← __thread 变量的副本
├──────────────────────────────┤
│                              │
│    线程栈(默认 8MB)          │  ← 向下增长
│                              │
└──────────────────────────────┘  低地址


								High addresses 高地址	
┌──────────────────────────────┐  
│    struct pthread (TCB)      │  ← 线程描述表:pthread_t 就是这个结构体的首地址
├──────────────────────────────┤
│   		TLS				   │  ← 线程局部存储: 存储__thread 变量的副本
├──────────────────────────────┤
│                              │
│      Thread stack			   │  ← 线程栈: 向下增长
│      (default 8MB)           │
└──────────────────────────────┘ 
							     Low addresses 低地址

A.struct pthread -TCB

struct pthread 
{
    /* ---- 标识信息 ---- */
    struct pthread *self;         // ★指向自身(用于 %fs:0 快速定位)
    pid_t tid;                    // ★内核分配的 LWP ID(gettid 的值)
    pid_t pid;                    // ★进程 ID(tgid)

    /* ---- 线程栈信息 ---- */
    void *stackblock;             // ★栈内存的起始地址
    size_t stackblock_size;       // 栈大小(默认 8MB)
    void *guardaddr;              // 栈底保护页地址(防止栈溢出)
    size_t guardsize;             // 保护页大小(默认 4KB)

    /* ---- 生命周期状态 ---- */
    int cancel_state;             // 取消状态(是否允许被取消)
    int cancel_type;              // 取消类型(延迟/异步)
    int canceled;                 // 是否已被取消
    int detached;                 // 是否已 detach
    int joinid;                   // 谁在 join 我

    /* ---- 同步相关 ---- */
    struct pthread_mutex *mutex_list;  // 持有的互斥锁链表
    futex_t *robust_list;         // 健壮互斥锁列表

    /* ---- 清理与退出 ---- */
    struct _pthread_cleanup_buffer *cleanup;  // 清理处理器链表
    void *(*start_routine)(void *);           // 线程入口函数
    void *arg;                    // 入口函数参数
    void *result;                 // ★线程返回值(pthread_exit 的参数)

    /* ---- 信号相关 ---- */
    sigset_t sigmask;             // 线程信号掩码
    sigset_t sigpending;          // 挂起信号集

    /* ---- TLS 相关 ---- */
    void *specific[LEVEL1_SIZE];  // pthread_key_create 的动态 TLS 数组
    dtv_t *dtv;                   // 动态线程向量(Dynamic Thread Vector)

    /* ---- errno ---- */
    int err_no;                   // 本线程的 errno(通过 TLS 访问)

    /* ---- 调度信息 ---- */
    int schedpolicy;              // 调度策略
    int schedparam;               // 调度参数
    int flags;                    // 线程标志位

    /* ... 更多字段 ... */
};

关键字段:

字段作用
self放在 TCB 的第一个位置,%fs:0 直接读到 TCB 自身地址。
tid创建时由内核通过 CLONE_CHILD_SETTID 写入,是用户态访问内核 LWP 的唯一凭证
stackblock记录栈的起始地址,线程退出时据此释放内存
dtv动态线程向量,管理动态链接库中声明的 __thread 变量
specific[]pthread_key_create() 创建的动态 TLS 槽位
cleanuppthread_cleanup_push() 注册的清理函数链表
resultpthread_exit() 或线程函数返回值,供 pthread_join() 获取

B. 线程局部存储 -TLS

TLS解决的问题:全局变量和静态变量在所有线程间共享,但是很多场景需要"看起来像全局变量,实际上每个线程各有一份"的数据。

例如: errno、日志缓冲区、线程私有缓存等。

关键字:__thread 和 thread_local

// C 语言(GCC 扩展)
__thread int counter = 0; //每个线程都能看见,但是y独立的一份

// C++11 标准
thread_local std::string buffer;

C. 线程栈 -Thread stack

线程栈存放的内容如下所示:

┌─────────────────────────────────────┐  Higher addresses
│  Function arguments 				  │  ←  函数参数
├─────────────────────────────────────┤
│  Return address (pushed by call)    │  ← 返回地址(call 指令压入) 
├─────────────────────────────────────┤
│  Saved registers (RBP, RBX, etc.)   │  ← 保存的寄存器(RBP、RBX 等) 
├─────────────────────────────────────┤
│  Local variables (arrays, structs)  │  ← 局部变量(数组、结构体等)  
├─────────────────────────────────────┤
│  Temporary values (intermediate)    │  ←  临时值(函数调用的中间结果)   
└─────────────────────────────────────┘  

线程栈的介绍:

- 用户栈:局部变量、函数参数、返回地址全在这里,局部变量。  
  
  ·对于主线程的用户栈,位于虚拟地址空间的栈区,能够动态增长,在可执行程序加载时进行分配。
  ·对于子线程的用户栈,位于虚拟地址空间的共享区,大小固定,由mmap进行申请
  ·用户栈通过task_struct中的mm_struct结构体和vm_area_struct进行管理。
  
- 内核栈:线程通过系统调用进入内核时,走自己独立的内核栈。 
  
  ·在内核空间中都有的固定大小的栈。
  ·内核栈通过task_struct中的stack指针进行管理

2.6.5 线程的完整流程

A.线程创建的完整流程

用户空间                              内核空间
   │                                    │
   │  1. mmap() 分配线程栈 + TLS          │
   │  2. 初始化 TCB (struct pthread)     │
   │  3. 调用 clone() 系统调用 ──────────→│
   │     flags: 						|
   |     CLONE_VM   					|  4. 创建 task_struct
   │     CLONE_FILES     				│  5. 根据 flags 共享资源:
   │     CLONE_SIGHAND       			│     - CLONE_VM → 共享 mm(地址空间)
   │      ...             			    │     - CLONE_FILES → 共享 files_struct(文件描述符表)
   │                                    │     - CLONE_SIGHAND → 共享 signal_struct(信号处理)
   |                                    │     ...
   |								    │
   │                                    │  6. 设置 tgid = 父线程 tgid
   │                                    │  7. 设置 group_leader
   │                                    │  8. 加入调度队列
   │  ←──────── 返回新线程的 LWP ID ────  │
   │  9. 记录 TCB 与 LWP 的映射           │
   │                                    │  
   │                                    │ 10. 新线程被调度后开始执行 start_routine
   │                                    │ 

B.线程销毁的完整流程

用户空间                              内核空间
   │                                    │
   │  1. 线程函数返回,调用pthread_exit()   │
   │                				    │
   │  2. 执行清理处理器                   │
   │  3. 调用 exit() 系统调用 ──────────→ │
   │                                    │  4. 内核释放 task_struct
   │                                    │  5. 如果设置了 CLONE_CHILD_CLEARTID,
   │                                    │     清零 TCB 中的 tid 字段
   │                                    │ 
   │  ←──────────────────────────────── │  6. futex 唤醒等待 join 的线程
   |  7. join 线程被唤醒,获取返回值  	     |
   │  8. 回收线程栈和 TCB(mmap 释放)      │

2.7 thread的封装

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <cstring>
#include <cstdlib>

// 基于 C 接口 (pthread) 的 C++ 线程封装
namespace ThreadModule
{
    // 已绑参的可调用对象类型: 无参无返回
    using func_t = std::function<void()>;

    // 内部上下文: 承载可调用对象 + 线程名
    // 独立分配在堆上, 是为了把生命周期与 Thread 对象解耦
    // (线程可能在 Thread 对象析构后仍在运行, 不能让回调引用悬空)
    class ThreadData
    {
        friend class Thread; // 须带 class: 否则 Thread 尚未声明, 会被当成友元函数声明

    public:
        ThreadData(const func_t &func, const std::string &name)
            : _func(func),
              _name(name)
        {
        }

    private:
        func_t _func;
        std::string _name;
    };

    // 线程状态: 防止非法操作 (重复 join / join 已 detach 的线程等)
    enum class ThreadStatus
    {
        NEW,      // 已构造, 未启动
        RUNNING,  // 运行中
        DETACHED, // 已分离
        JOINED    // 已回收
    };

    // 线程自动编号: inline 函数 + static 局部变量
    // C++11 下 inline 函数的 static 局部量跨编译单元共享, 头文件安全
    inline int NextThreadId()
    {
        static int id = 1;
        return id++;
    }

    class Thread
    {
    public:
        // 接受任意可调用对象 + 参数, 用 std::bind 绑参后转为 std::function<void()>
        template <typename F, typename... Args>
        Thread(const std::string &name, F &&f, Args &&...args)
        {
            _name = name + "-" + std::to_string(NextThreadId());
            _status = ThreadStatus::NEW;
            _td = new ThreadData(
                std::bind(std::forward<F>(f), std::forward<Args>(args)...),
                _name);
        }

        ~Thread()
        {
            // 析构策略 B : 仍在运行就自动 detach, 保证不泄漏
            if (_status == ThreadStatus::RUNNING)
            {
                pthread_detach(_tid); // 不断言, 容错 (线程可能已结束)
            }
            // 从未启动: 跳板不会运行, 自己清理 _td
            if (_status == ThreadStatus::NEW && _td != nullptr)
            {
                delete _td;
                _td = nullptr;
            }
            // 已启动 (RUNNING/DETACHED/JOINED): _td 由跳板结束时释放, 此处不动
        }

        // 禁用拷贝: 一个 pthread_t 不能被两个对象管理 (double join / double free)
        Thread(const Thread &) = delete;
        Thread &operator=(const Thread &) = delete;

        // 启动线程 (构造与启动分离, 便于统一管理一批线程)
        void start()
        {
            if (_status != ThreadStatus::NEW)
            {
                std::cerr << "[Thread:" << _name << "] already started\n";
                return;
            }
            int n = pthread_create(&_tid, nullptr, start_routine, _td);
            if (n != 0)
            {
                std::cerr << "pthread_create error: " << strerror(n) << "\n";
                std::abort();
            }
            _status = ThreadStatus::RUNNING;
        }

        // 等待回收
        void join()
        {
            if (_status != ThreadStatus::RUNNING)
            {
                std::cerr << "[Thread:" << _name << "] cannot join (status not RUNNING)\n";
                return;
            }
            int n = pthread_join(_tid, nullptr);
            if (n != 0)
            {
                std::cerr << "pthread_join error: " << strerror(n) << "\n";
                std::abort();
            }
            _status = ThreadStatus::JOINED;
            _td = nullptr; // _td 已被跳板释放, 仅置空标记
        }

        // 分离
        void detach()
        {
            if (_status != ThreadStatus::RUNNING)
            {
                std::cerr << "[Thread:" << _name << "] cannot detach (status not RUNNING)\n";
                return;
            }
            int n = pthread_detach(_tid);
            if (n != 0)
            {
                std::cerr << "pthread_detach error: " << strerror(n) << "\n";
                std::abort();
            }
            _status = ThreadStatus::DETACHED;
            _td = nullptr; // _td 由跳板结束时释放
        }

        const std::string &name() const { return _name; }
        bool isRunning() const { return _status == ThreadStatus::RUNNING; }

    private:
        // 跳板: 签名必须匹配 pthread 要求的 void*(*)(void*)
        // 由于非 static 成员函数隐含 this, 签名不匹配, 因此必须用 static
        static void *start_routine(void *arg)
        {
            ThreadData *td = static_cast<ThreadData *>(arg);
            td->_func(); // 执行用户回调
            delete td;   // 跳板作为最终消费者, 释放上下文
            return nullptr;
        }

    private:
        pthread_t _tid;
        std::string _name; // 自己保留一份, name() 在 join/detach 后仍可用
        ThreadStatus _status;
        ThreadData *_td = nullptr;
    };

} // namespace ThreadModule
第一节:初识n8n与下一代工作流自动化 本章介绍了n8n的Fair-code理念与节点式架构,对比了其与Zapier/Make的核心差异,并引导读者在n8n Cloud上完成首次界面导览,建立了工作流自动化的宏观认知。 阅读详情

相关推荐

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系:rl命名空间下,主要有八大模块。搞定VS后将逐个拆解。

qq_34839823的博客 1736

【Linux学习笔记】线程概念和控制(二)

本文主要介绍了Linux线程的相关概念和控制方法。首先分析了线程相比进程的优点(创建代价小、切换开销低、资源占用少)和缺点(性能损失、健壮性降低、编程难度高)。接着详细讲解了Linux中进程与线程的关系,包括线程共享的进程资源和线程独有的私有数据。然后重点介绍了POSIX线程库的使用方法,包括线程创建(pthread_create)、线程终止(pthread_exit)和线程取消(pthread_cancel)等核心函数。最后解释了为什么需要线程等待,以及主线程与其他线程的区别。文章通过代码示例和系统命令(

大白的编程日记. 1618

避坑指南:DDR4内存条Margin测试Fail?从Intel RMT工具到PCB Layout的完整排错思路

本文深入探讨了DDR4内存条在Margin测试中Fail的完整排错思路,从Intel RMT工具分析到PCB Layout优化。通过实战案例,详细解析了信号完整性问题的诊断与解决方案,包括拓扑结构影响、传输线参数优化及HyperLynx仿真验证,为硬件设计工程师提供了宝贵的避坑指南。

weixin_42530458的博客 451

学习笔记—Linux—线程概念&线程控制

本文摘要: 线程是进程内部的执行分支,Linux通过轻量级进程(LWP)模拟线程,复用PCB实现线程调度。线程共享进程地址空间,但各自维护独立的寄存器、栈帧等。使用pthread库控制线程,提供创建(pthread_create)、退出(pthread_exit)、等待(pthread_join)等接口。线程优点包括创建代价低、切换开销小、可并行处理I/O与计算任务;缺点涉及调度资源竞争和代码复杂度。Linux线程本质是共享资源的PCB,通过页表实现资源分配,线程局部存储(__thread)可实现变量独立。

2401_83231468的博客 395

英飞凌TC3xx DSADC旋变软解码实战:从示波器波形到VX1000数据,手把手教你避坑

本文详细介绍了英飞凌TC3xx系列微控制器的DSADC模块在电机旋变软解码中的实战应用。从示波器波形分析到VX1000数据采集,提供了一套完整的调试方法论,帮助工程师快速定位和解决常见问题,提升解码精度和系统性能。

weixin_30598225的博客 517

Zotero PDF Translate插件实战:DeepL API免费申请与高效翻译配置指南

本文提供Zotero PDF Translate插件配置DeepL API的完整实战指南。详细介绍了如何免费申请DeepL API密钥,解决国内访问问题,并在插件中完成精细配置,实现文献阅读时的无缝划词翻译,大幅提升科研人员和学生的英文文献阅读效率。

rum55的博客 458

linux笔记归纳11:线程概念与控制

进程切换:保存旧进程CPU硬件上下文,切换新进程的PCB上下文,修改CR3寄存器中的页目录。在ELF文件中,同一个页框内的地址,前二十位都是一样的,属于同一个4KB中(局部性原理)在windows系统中,线程在内核中也需要管理,先描述,再组织,该描述结构体叫做TCB。即用户级别的库,pthread库是动态库,格式为ELF,可执行程序的文件格式也为ELF。将一个进程的所有线程TCB,用一种数据结构组织在一起,存放在该进程的PCB中(复杂)

yonakaame的博客 256

华为OD机试真题 新系统2026-04-01 Java&Go【计算数列位置N的值】

如果其前面的M个元素中,存在值相同的元素,则该位置上的数值等于前面M个数中最大的数值与最小的数值之如果其前面的M个元素中,不存在值相同的元素,则该位置上的数值等于前面M个数中最大的数值与最小的数值之差;1、输入M、N两个数,则按照以下规则形成一个数列;两个整数,用逗号分割,分别表示M和N。一个整数,表示数列第N个位置上的数值。2、数列的前M个元素的值为1到M;请计算该数列第N个位置上的数值。N取值范围:1<= N<=50。M取值范围:3<=M<=10。

MISAYAONE的博客 2056

【Linux学习笔记】线程概念和控制(三)

本文介绍了Linux线程的终止、等待和分离操作。线程可通过return、pthread_exit或pthread_cancel终止,需要注意返回指针的内存管理。pthread_join用于线程等待,防止资源泄漏。pthread_detach可将线程设置为分离状态,自动释放资源。文章还分析了线程ID的本质,指出在NPTL实现中线程ID是进程地址空间上的一个地址,并解释了用户线程与内核轻量级进程(LWP)的联动机制。最后通过mmap系统调用和glibc源码,说明了线程栈的创建和管理方式。这些知识对于深入理解Li

大白的编程日记. 1218

吉林大学数据库期末考试试卷讲解

给出几套期末真题及解析,给出复习建议。

一杯水果茶!足矣~ 3142

VTK GPU 加速渲染技术

/ 创建颜色工具// 创建立方体数据// 创建OpenGL映射器(注意使用vtkOpenGLPolyDataMapper而不是常规的vtkPolyDataMapper)// 启用VBO(顶点缓冲对象)// 声明几何体是静态的以获得最佳性能// 创建actor// 创建渲染器、渲染窗口和交互器renderWindow->SetWindowName("VTK自定义着色器示例");// 添加actor到渲染器。

深之JohnChen的专栏 1242

【Linux学习笔记】线程概念与控制(一)

本文介绍了Linux线程的基本概念与控制。首先解释了线程是进程内部的执行序列,Linux中线程本质是轻量化的PCB。其次详细讲解了分页式存储管理机制,包括虚拟地址与页表的作用、物理内存管理方式、多级页表结构及其地址转换过程,并分析了TLB快表提升查询效率的原理。最后说明了缺页异常的处理机制,包括不同类型缺页中断的处理方式。文章通过图解和实例,系统性地阐述了Linux线程管理和内存管理的核心原理。

大白的编程日记. 886

C++软件调试与异常排查从入门到精通专栏介绍与文章汇总

根据近几年排查软件异常的实践与经验,系统地讲解了C++软件异常常见原因与常用排查方法,以图文并茂的方式给出具体的分析实例,带领大家逐步掌握C++软件异常排查的相关技术与要领。

dvlinker的技术专栏 23万+

Linux 学习笔记—线程间通信的概念和线程控制函数及实例

1 线程间通信 线程间无需特别的手段进行通信,由于线程间能够共享数据结构,也就是一个全局变量能够被两个线程同一时候使用。只是要注意的是线程间须要做好同步,一般用mutex。线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。 线程的基本概念 线程的定义: 线程也被称为轻量进程(LWP)计算机科学术语,指运行中的程序的调度单位。线程是进程中的实体,一个进程可以拥有多...

c+c++的博客 191

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

【linux】线程概念与控制

详细讲解和实操了linux下线程概念与使用

You build it, you run it 3347

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【Linux】线程概念与控制

下面是一个简化图有了页帧,该如何管理呢?先描述再组织,操作系统引入了。

2401_86449430的博客 1万+

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

【Linux线程】Linux线程编程基础:概念、创建与管理

在当今的软件开发领域,多线程编程已经成为了一种不可或缺的技术。特别是在Linux操作系统环境下,多线程编程更是被广泛地应用于各种高性能、高并发的应用场景中。然而,多线程编程也是一项极具挑战性的任务,它要求开发者具备扎实的编程基础,对并发控制、资源管理等有深入的理解

EterNity_TiMe_的计算机学习之路 3625

【Linux】Linux线程概念和线程控制

本文介绍了Linux中线程的概念和线程控制--线程创建,终止,等待,分离

qq_67582098的博客 2602

【Linux系统】初见线程,概念与控制

本文深入解析了Linux线程的概念、实现原理及控制方法。主要内容包括:1. 线程是进程内部的执行分支,在Linux中通过轻量级进程(LWP)实现,使用PCB结构体描述;2. 线程共享进程资源,但拥有独立的执行流和栈空间;3. 通过pthread库提供的接口(如pthread_create、pthread_join等)实现线程创建、终止、等待和分离;4. 详细分析了线程ID的本质、地址空间布局以及线程局部存储特性;5. 比较了线程与进程在资源管理、切换开销等方面的差异。文章还探讨了页表机制、物理内存管理、TL

记录学习与成长 3847

【Linux探索学习】第二十九弹——线程概念:Linux线程的基本概念与线程控制详解

【Linux探索学习】第二十九弹:线程的基本概念和线程控制的相关知识点,理清线程与进程的关系与区别!!

2301_80220607的博客 5572

Linux:线程概念 | 线程控制

当CPU调度执行该“”进程“时,只会执行原本进程中的一部分代码和数据,执行我们要执行任务的一部分任务,我们将这种比传统“进程”更加轻量化的进程就称为线程!)下,其中一些线程抢占锁的能力是非常强的,导致其他线程长时间无法获得锁资源,导致线程饥饿问题。我们也将这种线程称为轻量级进程!在Linux中,如果操作系统真的支持线程,此时在计算机中必然存在大量的线程,所以OS需要对线程进行管理。如果不关心线程的返回值,join是一种负担,这个时候,我们可以将线程设置为分离状态,告诉系统,当线程退出时,自动释放线程资源!

Zhenyu_Coder的博客 2506

Linux线程-概念和控制

Linux多线程-概念和控制零、前言一、Linux线程概念1、什么是线程2、vfork函数/pthread线程库3、线程优缺点及其他分析二、Linux进程VS线程1、进程和线程三、Linux线程控制1、POSIX线程库2、线程创建3、线程ID及线程地址空间布局4、线程终止5、线程等待6、线程分离 零、前言 本章主要讲解学习Linux中的线程 一、Linux线程概念 1、什么是线程 概念: 在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是“一个进程内部的控制序列”

~ 2609

【Linux】多线程1——线程概念与线程控制

初步认识线程的概念,并熟悉线程控制接口

Pursue quality, not quantity. 1613

Linux的线程概念与控制

首先Linux内核不区分"进程"和"线程",统一用task_struct管理。CPU处理的是一个个task_struct。进程1个或多个task_struct(PCB) +代码和数据线程(轻量级进程) =1个task_struct(PCB) (它通过指针共享了其所属进程的代码和数据我们之前讲的传统的进程是一个主线程。一个进程的多个task_struct(PCB),被认为是多个轻量级进程,主轻量级进程的id即lwp(light weight peocess)与进程的pid相同。

博客 | 复习知识点 + 练习 + 最终的领悟 | 全力以赴 1427

【Linux】线程概念

目录 1. 线程 1.1. 线程概念 1.2. Linux下的线程 线程的优点 线程的缺点 1.3. 进程与线程 2.线程控制 2.1. 线程创建 2.2. 线程等待 2.3. 线程终止 2.4. 线程分离 3.线程ID 1. 线程 1.1. 线程概念 一般而言,线程是在进程内部运行的一个执行分支(执行流),属于进程的一部分,粒度要比进程更加细和轻量化。 一个进程内是可能存在多个进程的。 那么操作系统内就存在更多的线程,所以操作系统管理线程的方式依旧是:先描述,再组

Edward_Asia的博客 2054

【Linux】线程概念与线程控制

线程的概念与控制,页表相关内容

weixin_69380220的博客 1288
上一篇: 【Linux笔记】Linux库的制作与原理
下一篇: 【Linux笔记】线程同步与互斥
高山有多高
博客等级 码龄2年 844粉丝 89原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值