最近有日子没写博客了,这段时间有点事忙活一阵子,好在已经接近尾声。也该轮到投些时间好好研究下真刀真枪的东西,干些有意义的事。这两天抽时间继续往下看了看 Linux 内核和 Unix 编程的书,边看边琢磨,想到个关于进程在 fork 子进程或 pthread 出 lwp 时父亲进程的栈段是如何处理的问题,结合 Linux 内核的说明对这个问题有了明确的理解,在此做个笔记。大家也一起研究、分享下~
历史上来说,*nix 里的 C 程序进程由以下几部分组成:
- 正文段。也有叫代码段的。存放着 CPU 执行的机器指令。它一般是共享、只读的。
- 初始化数据段。存放着程序中明确赋初值的变量。
- 非初始化数据段。也叫 bss ,存放着 C 函数之外的变量,内核会初始它为 0 填充。
- 栈段。存放 auto 变量或函数调用传递的信息。包括返回值、实参,调用者上下文(忽然想到,闭包变量会用这个传递么?请教 guru),这些变量会存在一个 stack frame 中。
- 堆段。用于 sbrk (malloc) 等动态内存分配的。
这图说明了这些段的典型的布局。在 x86 CPU 的 Linux 中栈底位于 0xc0000000 开始,该地址以上存储的就是内核代码了(那段线性地址直接映射到物理地址)。当我看到这里的时候,困惑的问题是,在这种段结构中,当我们父进程生成多进程/线程的子进程时,linux 内核对这个父进程的 stack 段是怎么处理了,来保证每个不同进程/线程中的方法调用时,用 stack 来传递的调用信息不会混乱,怎么保证竞争条件下的正确入/出栈顺序?呵呵,现在看来当时的想法比较可笑了。正确理解如下所述。
*nix 有3种进程创建方式:
- fork。内核为父进程创建副本,即子进程。传统情况下,该子进程将获得父进程完整复制,包括数据段(初始化和bss 段)和堆栈段。但是,由于 fork 之后经常跟着就是 execve 系统调用,因此现在的 *nix 会使用 COW(写时复制) 方式来 fork 子进程,也就是这些区域暂时不复制,并由内核将它们只读,当父/子进程对他们修改时,就将修改的部分保存在本次修改操作的进程地址空间中,通常单位是一页。内核为了提高性能,如果父子进程在同一颗 CPU 上的话呢(同 CPU 进程队列),会在 TASK_RUNNING 进程链中将子进程放在放在父进程的前边,这样可减少不必要的 COW 开销。此外还有一些其它属性也将由子进程继承,如实际、有效用户/组 ID,会话 ID,工作目录,环境,连接的共享存储段,资源限制等等。
- lwp。轻量进程允许父子进程共享内核的在部分数据,页表(可共享全局数据),文件描述符表等。pthead 就是通过 lwp 实现的。
- vfork。apue2中将 vfork 单提了来,个人觉得实际和 fork 是一要的,只不过是简版的。通过它创建的子进程能够共享父进程内存地址空间,但为了避免父子混乱,子进程暂时阻塞了父进程执行,直到子进程退出父进程再在此基础上继续执行。
实际上看到这里,已经能够很清楚的解释我上面的疑问了,呵呵。fork 方式会独立出父子进程,vfork 会顺序执行。那么 fork/lwp 的具体细节内核是怎么做到的呢,继续挖。
Linux 通过 clone 系统调用来创建 lwp,而 clone、fork、vfork 都是由 do_fork 内核函数来统一处理完成的,而 do_fork 又是由 copy_process 函数来完成功能的。再看一眼上面我最初想到的问题,已经知道 fork 会出来两个独立的用户进程空间,因此父子进程的栈段肯定不重复。但 lwp 怎么保证栈段不重复的呢,就是通过 clone 系统调用的 child_stack、tls 和 flags 参数来控制的,详细说明看文档和内核源码吧。Linux 中子进程的具体创建步骤说明可见《Understanding The Linux Kernel》 3th 的 P123 很详尽有看起来也有力道,呵呵。
现在关于这个 tls 还有没理解的地方,在 GDT 中共有 3 个 TLS ,段选择符 0x33 - 0x43(那也就是说只允许最多 3 个线程局部数据段),这个 clone 的 tls 参数是传递的什么值,是 GDT 中 tls 的段描述符地址么?
研究的还很 top,很多知识点很模糊 ,且待我继续深入~ 期待哪位牛人能够赐教一二。
// 2009.04.21 21:50 添加 ////
作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
// 2009.04.21 21:52 添加 ////
找插图的时候无意搜到一篇很好的文章,推荐之。
缓冲区溢出深入理解
// 2010.01.06 12:49 添加 ////
全局变量 a[10] 定义区别在 elf 目标文件 .data 和 .bss 段内分配对比。
int a[10]
text data bss dec hex
836 260 72 1168 490
int a[10] = {0};
text data bss dec hex
836 260 72 1168 490
int a[10] = {1};
text data bss dec hex
836 324 8 1168 490
int a[10] = {1, 2, 4, 5, 6, 7, 8, 9, 10};
text data bss dec hex
836 324 8 1168 490
- 大小: 17.3 KB
分享到:
相关推荐
linux系统下,C语言多线程多进程编程
linux unix 进程 线程linux unix 进程 线程linux unix 进程 线程linux unix 进程 线程linux unix 进程 线程linux unix 进程 线程
代码目的是比较write和printf多路写性能。首先fork生成子进程,并且在子进程中重定向标准输出;然后在父子进程中各创建10个线程分别利用printf和write进行写操作;最后通过返回值比较两者性能。
Linux下多线程及多进程及同步与互斥编程详细介绍
LINUX系统下多线程与多进程性能分析.pdf
在linux上分别用多进程和多线程实现的同步互斥操作(源代码)
服务器段软件需要一个进程接收客户端发送的文件,并将接收到的数据保存成一份文件。程序运行之后,用户可以随时终止程序的运行(比如按下CTRL+C),要求两个进程P1和P2在结束前将各自读取的报文数量(N_rev_P1、N_...
关于多进程与多线程的性能分析论文,作者:周丽 焦程波 兰巨龙
linux下多进程、多线程编程 更多资源请访问http://www.59186618.com
多线程模拟进程调度多线程模拟进程调度多线程模拟进程调度多线程模拟进程调度
我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空 间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用...
(Linux C)利用多进程或多线程模拟实现生产者/消费者问题。
linux下的C\C++多进程多线程编程实例详解 1、多进程编程 #include #include #include <unistd> int main() { pid_t child_pid; /* 创建一个子进程 */ child_pid = fork(); if(child_pid == 0) { printf...
linux系统下建立多线程程序设计,完成UDP网络通信的发送与接收,包括总结与源代码,实测效果可见链接https://blog.csdn.net/zxp121127/article/details/78506081
PPT文档及相应C源码 linux串口编程 linux进程间通信 linux进程间通信 linux多线程编程 linux网络编程
本文我们将介绍在Linux 下编写多进程和多线程程序的一些初步知识。 1 引言 对于没有接触过 Unix/Linux 操作系统的人来说,fork 是最难理解的概念之一:它执行 一次却返回两个值。fork 函数是Unix 系统最杰出的成就之...
在Linux下进行多线程编程时,首先要创建线程,然后,要掌握线程的结束方法,线程的参数传递及线程的标识等基本概念,在此基础上,熟悉线程互斥锁的概念及其编程。
全双工邮箱通讯,两个程序分别同过共享内存(邮箱)相互传输数据, 分别都有一个读写线程,读写个自通讯数据区域,一个读线程对应另一个进程的写线程;