菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
110
0

第6章.md

原创
05/13 14:22
阅读数 33144

进程

进程号

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体系结构中布局:

img

虚拟内存管理

程序仅有部分地址空间存在RAM中。

虚拟内存的规划之一是将每个程序使用的内存切割成小型的、固定大小的页单元。

进程尝试访问地址无页表目录对应时,将收到SIGSEGV信号。

进程页表改变条件

  • 栈向下增长超过之前曾到达的位置
  • 在heap中分配或释放内存时,通过调用brk()、sbrk()、malloc()函数族来提升program break的位置。
  • 调用shamt()连接共享内存
  • 当mmap()创建内存映射时,或者munmap()解除内存映射时。

img

栈和栈帧

栈:内核栈、用户栈

用户栈信息:

  • 函数实参、局部变量

  • 函数调用信息: 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);
}

image-20201218144324427

预想输出两次应该均为 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大小,会超出一般系统提供的栈的大小。

image-20201218150620296

发表评论

0/200
110 点赞
0 评论
收藏
为你推荐 换一批