进程和线程的共享
本文档可以说明以下几个问题:
问题一:多进程编程中,不同进程是否可以通过全局变量来通信?
问题二:多线程编程中,不同线程是否可以通过全局变量来通信?
在说明这两个问题之前,首先说明对于进程使用的内存的分段结构(高地址--->低地址):
环境变量: 存放进程的环境变量信息。
栈: 临时变量、返回地址、函数入参等。
堆: malloc就在这地方申请。
BSS段: 存放未初始化的全局变量,未初始化的静态变量(无论该静态变量在函数内定义还是函数外定义,都存放在这里)。
数据段(DS):存放初始化过的全局变量,初始化过的静态变量。
代码段(CS):存放程序的代码。
【问题一】
对于兄弟的进程或者父子进程中有一方使用了exec族函数(进程的代码、数据全部被替换,实际使用的物理内存也是重新申请的),进程的代码段、数据段、堆栈段都是独立的,没有任何关系,当然系统会为为他们各自都维护页目录和页表信息(每个进程有自己的页目录和页表)。
对于父子进程,假设我们在代码中定义了一个全局变量g_test =1;当使用fork()系统调用时,子进程使用的页目录和页表是复制父进程的,所有此时父子进程共享所有数据(当然fork()的返回值不一样,信号位图也不一样),所以这个时候父子进程读取全局变量g_test的值以及地址都是一样的,因为g_test是被共享的,g_test这块内存在两个进程的页表中都映射了同一块物理内存。
如果之后父子进程中有一方执行了写操作(比如子进程进行g_test++),子进程在在查找页表时会发现这页内存是共享的(内存引用计数大于1),这时候就触发了写时复制COW机制,系统会为子进程申请一页新内存,并拷贝父进程g_test所在内存页的数据到子进程新申请的内存中,并更新子进程中页表信息(g_test所对应的线性地址映射到新内存),然后再执行g_test++操作。
此时父子进程访问g_test时,虽然变量名一样,变量的线性地址一样(printf(&g_test)),但是他们的值是不一样的,因为这个时候,父子进程的g_test被映射到了不同的物理内存中。
【问题二】
首先对于不同进程的线程而言,他们没有什么关系的(代码段、数据段等都不同),所以不能通过全局变量来通信。
但是对于同一进程内的线程,他们是可以通过全局变量来通信的。
同一进程内的线程,他们都共享进程的代码段、数据段、BSS段。页目录和页表应该是使用进程的页目录和页表。
对于上面的描述,又产生一个新问题:我们在编写驱动程序的时候,对于全局变量的使用一般都要考虑并发,也就是说这时候全局变量是共享的,这和问题一有出入。
实际上,当进程进入内核执行时,所有进程共享内核的代码段和数据段,所以在内核中,全局变量是共享的,访问时要考虑并发和竞态。(所有进程共享内核的页目录和页表结构)