Linux 学习笔记(三十一):线程概念、底层分页机制与初体验

一、 什么是线程?

在 Linux 中,线程是"轻量级进程(LWP)",是 CPU 调度的最小单位。一个进程可以包含多个线程。

  • 共享的资源:代码段、数据段(全局变量)、堆区、文件描述符表。

  • 私有的资源 :线程 ID、寄存器上下文、独立的栈(Stack)、errno、信号屏蔽字。

    线程切换极快(不需要切换页表和刷新 TLB),但也极其危险(一个线程崩溃会导致整个进程崩溃)。

二、 底层存储:为什么需要虚拟内存与多级页表?

操作系统通过"页表"将虚拟地址映射到物理地址。

  • 单级页表在 32 位系统下需要占用 4MB 且必须连续,极度浪费。

  • 因此引入了多级页表(页目录)。利用局部性原理,只加载需要的页表项。

  • 缺页中断(Page Fault) :当 CPU 访问的虚拟地址在页表中不存在时触发。OS 会挂起当前进程,去磁盘把数据读入物理内存,建立映射,然后恢复进程运行。这正是 malloc 申请大内存时,"延迟分配"的底层原理。

三、 实战:创建第一个线程

cpp 复制代码
#include <pthread.h>
pthread_create(&tid, NULL, thread_run, NULL);

注意: 编译时必须加上 -lpthread 链接库。

四、 实战验证与 LWP

使用 ps -aL 查看系统进程和线程信息,可以看到:

  • 两个执行流有相同的 PID,但 LWP 不同。Linux 内核并不区分进程和线程,它们底层都是 task_struct,只是共享资源的程度不同。

五、 实战:验证全局变量共享

新建子线程修改全局变量 g_val,主线程随后读取。发现主线程读到了修改后的值,完美验证了线程间数据(数据段)是共享的。

自我实战操作

先不用 C++ 的高大上封装,直接用 C 语言 POSIX 线程库。

bash 复制代码
cd ~/linux_study
mkdir thread_test
cd thread_test
实战 1:创造一个线程(并观察 LWP)
bash 复制代码
vim thread_create.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thread_run(void *arg) {
    while(1) {
        printf("我是新线程,我的 ID 是: %lu\n", pthread_self());
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    // 创建线程,执行 thread_run 函数
    pthread_create(&tid, NULL, thread_run, NULL);
    
    while(1) {
        printf("我是主线程,我的 ID 是: %lu\n", pthread_self());
        sleep(1);
    }
    return 0;
}

编译并运行(注意坑点):

bash 复制代码
# 必须加 -lpthread,否则会报错 undefined reference to `pthread_create'
gcc thread_create.c -o thread_create -lpthread
./thread_create

👉 现象 :屏幕上会交错打印"主线程"和"新线程"的内容。

👉 神级操作 :新开一个终端,输入:

bash 复制代码
ps -aL | grep thread_create | head -n 1 && ps -aL | grep thread_create

你会看到什么?

  1. 有两个名为 thread_create 的进程。

  2. 它们的 PID 相同(因为是同一个进程)。

  3. 它们的 LWP(轻量级进程 ID,也就是线程 ID)不同!

    (这就证明了:Linux 内核并不区分进程和线程,统一用 task_struct 表示,只是共享了某些资源的 task_struct 就叫做线程。)

实战 2:验证"共享与私有"
bash 复制代码
vim thread_share.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int g_val = 100; // 全局变量(共享)

void *thread_run(void *arg) {
    g_val = 200; // 子线程修改全局变量
    printf("子线程:修改全局变量 g_val = %d\n", g_val);
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_run, NULL);
    
    sleep(1); // 等子线程先跑完
    printf("主线程:看到全局变量 g_val = %d\n", g_val); // 主线程读全局变量
    return 0;
}
bash 复制代码
 gcc thread_share.c -o thread_share -lpthread && ./thread_share

👉 现象 :主线程打印出来的是 200。这证明了全局变量是共享的 。如果是 fork 创建的子进程,这里打印的绝对还是 100(写时拷贝)。这就是线程间通信极其方便、但也极其危险的原因。

相关推荐
外收内放1 小时前
FastAPI猜字谜实战(5):会话管理
开发语言·python·学习·fastapi
Hello World . .1 小时前
【嵌入式学习笔记 Day 1】
linux·数据结构·笔记
小向是个Der1 小时前
P02_你的变量住在哪里_教学文档
经验分享·笔记·stm32·单片机
星恒随风1 小时前
Linux Socket UDP编程(一):手写UDP回显服务器与客户端,从socket、bind、recvfrom到网络字典服务器完整实战
linux·服务器·网络·笔记
H.莓飛1 小时前
【数据结构】归并与外排序
linux·服务器·c语言·开发语言·数据结构·visual studio
HY小宝F1 小时前
智能摄像头系统 30 天学习计划 Day 09:用 RTSP 把视频送到网络
网络·学习·音视频
junwua1 小时前
qnx hypervisor 的中文版
linux·运维·服务器·自动驾驶
小猪佩奇TONY1 小时前
Linux 内核学习(19) --- linux waitqueue
linux·学习·waitqueue
m4Rk_1 小时前
【论文阅读】Agent记忆机制(111):FEB-30——别再只用问答准确率评估 Agent Memory
论文阅读·人工智能·学习·开源·github