进程、线程、systemcall、协程与堆、栈

张开发
2026/4/16 3:42:28 15 分钟阅读

分享文章

进程、线程、systemcall、协程与堆、栈
1.Linux 中对进程的表示--task_struct内核在一段叫slab的专用内存池里分配task_struct。并且内核栈往往就紧挨着task_struct分配或者通过指针关联2.进程和线程的区别对于线程来讲其地址空间 mm_struct、目录信息 fs_struct、打开文件列表 files_struct 都是和创建它的任务共享的。对于进程来讲地址空间 mm_struct、挂载点 fs_struct、打开文件列表 files_struct 都要是独立拥有的都需要去申请内存并初始化它们。区分一个 Task 任务该叫线程还是该叫进程一般习惯上就看它是否有独立的地址空间3.进程的地址空间 mm_struct进程在运行的时候在用户态其所需要的代码全局变量数据以及 mmap 内存映射等全部都是通过 mm_struct 来进行内存查找和寻址主要的段如下程序段 (Text Segment)可执行文件代码的内存映射数据段 (Data Segment)可执行文件的已初始化全局变量的内存映射BSS段 (BSS Segment)未初始化的全局变量或者静态变量用零页初始化堆区 (Heap) : 存储动态内存分配匿名的内存映射栈区 (Stack) : 进程用户空间栈由编译器自动分配释放存放函数的参数值、局部变量的值等映射段(Memory Mapping Segment)任何内存映射文件扩展特征堆栈由程序员手动进行分配和释放自动分配和管理由程序员手动释放或由GC自动回收函数执行完毕后自动释放存储动态分配的对象和复杂数据结构存储基本数据类型和引用类型的地址存储函数调用栈、临时变量等缺点容易出现内存泄露、溢出等问题效率较低空间大小固定无法动态扩展无法灵活管理4.进程栈栈作用函数调用和多任务支持地址空间中的栈区指的是的进程栈属于用户态栈Linux 内核会根据入栈情况对栈区进行动态增长。但是并不是说栈区可以无限增长它也有最大限制RLIMIT_STACK(一般为 8M)。在函数调用时第一个进栈的是函数调用语句的下一条可执行语句的地址然后是函数的各个参数在大多数的C编译器中参数是由右往左入栈的然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后局部变量先出栈然后是参数最后栈顶指针指向最开始存的地址也就是主函数中的下一条指令程序由该点继续运行。EBP 是基址指针是保存调用者函数的地址。ESP是被调函数的指针Linux 中的各种栈进程栈 线程栈 内核栈 中断栈计算机组成原理—中央处理器CPU5.内核进程栈在每一个进程的生命周期中必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后这些内核代码所使用的栈并不是原先进程用户空间中的栈而是一个单独内核空间的栈这个称作进程内核栈。进程内核栈在进程创建的时候通过 slab 分配器从 thread_info_cache 缓存池中分配出来其大小为 THREAD_SIZE一般来说是一个页大小 4Kunion thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };thread_union 进程内核栈 和 task_struct 进程描述符有着紧密的联系。由于内核经常要访问 task_struct高效获取当前进程的描述符是一件非常重要的事情。因此内核将进程内核栈的头部一段空间用于存放 thread_info 结构体而此结构体中则记录了对应进程的描述符两者关系如下图对应内核函数为 dup_task_struct()有了上述关联结构后内核可以先获取到栈顶指针 esp然后通过 esp 来获取 thread_info。这里有一个小技巧直接将 esp 的地址与上 ~(THREAD_SIZE - 1) 后即可直接获得 thread_info 的地址。由于 thread_union 结构体是从 thread_info_cache 的 Slab 缓存池中申请出来的而 thread_info_cache 在 kmem_cache_create 创建的时候保证了地址是 THREAD_SIZE 对齐的。因此只需要对栈指针进行 THREAD_SIZE 对齐即可获得 thread_union 的地址也就获得了 thread_union 的地址。成功获取到 thread_info 后直接取出它的 task 成员就成功得到了 task_struct。其实上面这段描述也就是 current 宏的实现方法6.在Linux上运行python3 script.py组件本质职责PC (程序计数器)CPU内部寄存器存放下一条指令的虚拟地址RSP (栈指针)CPU内部寄存器存放当前栈顶的虚拟地址MMUCPU内部硬件把虚拟地址翻译成物理地址进程栈RAM中的内存区域存放函数调用帧、局部变量、返回地址内核栈RAM中的内存区域内核态代码使用的栈静态时一个进程被创建其task_struct被初始化。它的mm_struct里填好了页表地址它的内核栈被分配好它的用户态入口PC_start和栈顶RSP栈基址被保存在task_struct的上下文字段中。调度运行时操作系统选择这个进程根据它的task_struct恢复CPU的PC、RSP、页表根地址等寄存器。CPU开始执行用户代码。需要服务时用户代码执行到syscall指令CPU硬件自动保存当前的 PC/RSP指向用户代码。加载新的 PC/RSP指向内核代码和内核栈。完成用户态 → 内核态切换。在内核态CPU执行内核代码使用的是该进程的内核栈。内核可以轻松地通过current宏指向当前CPU正在运行的进程的task_struct来访问该进程的所有资源内存、文件等。服务完成时内核执行sysret指令CPU硬件自动恢复之前保存的用户态 PC/RSP。调度器需要保存“内核态的 PC 和 RSP”到task_struct完成内核态 → 用户态切换。程序继续执行仿佛什么都没有发生。再次被暂停时时间片用完发生中断。内核的调度器再次登场将CPU当前的 PC、RSP 等状态保存回该进程的task_struct然后选择下一个进程。7.syscall和sysret负责在用户态和内核态之间切换8.Python 的协程Python 3.5 的async/await是 无栈协程——它的“切换”本质是函数返回和回调而不是 RSP 的切换

更多文章