第6章.md
进程
进程号
getpid()
pid_t getpid(void)
Linux内核2.4与其更早版本,进程号上限32767。上限由内核常量PID_MAX定义。Linux2.6之后可更改/proc/sys/kernel/pid_max
。内核参数修改:/etc/sysctl.d/99-sysctl.conf
--> kernerl.pid_max=65535
。64位平台可达到2^22。
当进程号满之后,程序计数器将从300开始,而不是1。
getppid()
进程内存布局
- text段: 包含程序指令。只读属性。
- 初始化数据段:已初始化的全局变量和静态变量
- 未初始化(BSS)段:程序仅保存BSS段的位置和大小,运行时由程序加载器分配这一空间。
- stack
- heap
此处的段(segment)与硬件分段(segmentation)不同
应用程序二进制接口(ABI):规定程序运行时如何与内核或库函数提供的服务交换信息。(个人理解: 不同编译器有不同优化方式,即函数传参不同,寄存器使用不同,ABI保证不同编译器编译出的程序使用相同的库)。
C获取各段地址
c语言提供3个全局符号: etext edata end 分别获取本程序text段、bss段、初始化段结尾下一字节地址
显示声明:
extern char etext, edata, end;
各内存段在x86-32体系结构中布局:
虚拟内存管理
程序仅有部分地址空间存在RAM中。
虚拟内存的规划之一是将每个程序使用的内存切割成小型的、固定大小的页单元。
进程尝试访问地址无页表目录对应时,将收到SIGSEGV信号。
进程页表改变条件
- 栈向下增长超过之前曾到达的位置
- 在heap中分配或释放内存时,通过调用brk()、sbrk()、malloc()函数族来提升program break的位置。
- 调用shamt()连接共享内存
- 当mmap()创建内存映射时,或者munmap()解除内存映射时。
栈和栈帧
栈:内核栈、用户栈
用户栈信息:
-
函数实参、局部变量
-
函数调用信息: PC、寄存器副本
内核栈信息:
进程陷入内核态后,先把用户堆栈的地址保存到内核堆栈中,然后设置设置CPU堆栈寄存器为内核栈的地址,这样就完成了用户栈到内核栈的转换。进程由用户栈到内核栈转换时,进程的内核栈总是空的。每次从用户态陷入内核时,得到的内核栈都是空的,所以在进程陷入内核时,直接把内核栈顶地址给堆栈指针寄存器即可。
命令行参数
int main(int argc, char *argv[], char *env[])
{
return 0;
}
Linux的ARG_MAX曾固定为32页面大小。参数存储自己上限通过ARG_MAX限定,通过调用sysconf(__SC_ARG_MAC)确定上限值。
环境列表
访问使用全局变量char **environ;
getenv() 获得环境变量
char *getenv(const char*name);
获取环境变量的值。返回的字符串不应修改
putenv(), setenv() 设置环境变量
int putenv(char *string);
string --> “name=value” 字符串不应为自动变量,该字符串将成为环境一部分
int setenv(const char *name, const char *value, int overwrite);
setenv()将复制字符串值,overwrite==0时将不会覆盖已存在的环境变量
(个人感觉比putenv()更好,因为字符串复制了一份)
unsetenv() clearenv() 移除环境变量
int unsetenv(const char *name);
int clearenv(void);
清除所有环境变量
clearenv()
与unsetenv()
使用可能会导致内存泄漏。clearenv不能获得setenv新分配的字符串地址
非局部跳转
setjmp() longjmp()
#include <setjmp.h>
int setjmp(jmp_buf env);
Returns 0 on initial call, nonzero on return via longjmp()
void longjmp(jmp_buf env, int val);
longjmp()第一次返回setjmp位置,setjmp将返回0
longjmp()不能返回已释放的函数
优化编译器问题
编译器优化将重组程序指令执行。cpu可能并行一段无关程序。
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf env;
void doJump(int nvar, int rvar, int vvar);
int main(int argc, char *argv[])
{
int nvar;
register int rvar;
volatile int vvar;
nvar = 111;
rvar = 222;
vvar = 333;
if (setjmp(env) == 0) {
nvar = 777;
rvar = 888;
vvar = 999;
doJump(nvar, rvar, vvar);
} else {
printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
}
return 0;
}
void doJump(int nvar, int rvar, int vvar) {
printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
longjmp(env, 1);
}
预想输出两次应该均为 777 888 999
由于优化器对代码重组受到longjmp()影响,所以输出不一致。
解决: 在非局部跳转代码内使用显示声明volatile
声明,让编译器不要优化变量
习题
6-1
编译程序清单6-1中的程序,使用ls -l命令显示可执行文件的大小,解释为什么可执行文件的大小远小于10MB,但是程序中包含了一个10MB的数组?
#include <stdio.h>
char globalBuf[65535];
void overstack();
int main(int argc, char *argv[])
{
overstack();
return 0;
}
void overstack() {
char overBuf[1024*1024*10]; // 申请10MB大小的容量
}
编译: 17K大小,显然程序中有两个大的未初始化的数据段,这些数组在程序运行时才会被分配存储空间。只有被初始化的数据段才会使得程序体积变大。
char globalBuf[65535] = {0,1,2};
此时81K
char mbuf[10240000] = {0,10,0,1};
使用如上的声明方式,不占用数据段,数组中剩余部分的0不占用程序体积,数组中的内容在文本段,占用程序体积,变量记录在栈中,在运行中将文本段拷贝到栈中,现在是8.8k,注意实际上体积仍然变大了,因为文本段增加了。程序无法运行,因为overBuf在栈中申请10MB大小,会超出一般系统提供的栈的大小。