1. 引言
1:程序执行时,main是如何被调用的
2:命令行参数如何传递给新程序
3:典型的存储空间布局样式
4:如何分配额外的储存空间
5:进程如何使用环境变量
6:进程的不同终止方式
7:longjmp和setjmp函数以及它们和栈的交互方式
8:进程的资源限制
2.main函数
main函数的原型是int main(int argc, char* argv[]);
内核执行c程序的步骤
内核调用一个启动例程
可执行程序文件将此启动例程指定为程序的起始地址
启动例程从内核获取命令行参数和环境变量参数
运行可执行文件
3.进程终止
5种正常终止方式
1、从main函数返回
2、调用exit
- void exit (int __status)
? int atexit (void (*__func) (void))
? exit返回时会调用atexit或者on_exit注册的回调函数
- 执行一些清理动作后返回到内核
? 执行标准io库的清理关闭操作,对于所有打开的流调用fclose
3、调用_exit或_Exit
- void _Exit (int __status)
? _Exit返回时不调用atexit注册的回调函数
- 立即返回到内核,不清理
? 二者的区别
4、最后一个线程从启动例程中返回
5、从最后一个线程调用pthread_exit
3种异常终止
调用abort
接收到一个signal
最后一个线程对取消请求的响应
程序的启动和终止
4.命令行参数
argc
argv
ISO C和POSIX都要求argv[argc]为null
5.环境表
extern char** environ;
字符指针数组
6. 存储空间布局
正文段
CPU执行的指令部分
- 通常为只读,防止程序意外修改指令
- int a = 10;
- 存放程序中已初始化的全局变量(包含静态变量)的一块内存区域
- const char* a = "abc";
初始化数据段
程序中需要明确付初值的变量
可分为read write区域和read only区域
? "abc"在read only区域
? a在read write区域
未初始化数据段,bss: block started by symbol
long sum[1000];
存放程序中未初始化的全局变量(静态变量)的一块内存区域
栈
自动变量以及函数调用需要保存的信息
- 最近被调用的函数都会在栈上为其自动和临时分配存储空间
- 递归函数调用自身时,生成一个新的栈帧。因此,一次函数的调用实例不会影响下一次
堆
堆中进行动态内存分配
典型的存储器排列
测试案例:https://www.geeksforgeeks.org/memory-layout-of-c-program/
高地址顶部:命令行参数和环境变量
- 程序启动时,由内核获取
7.共享库
8.存储空间分配
malloc
void *malloc (size_t __size)
分配指定长度的存储区,初始值不确定
calloc
void *calloc (size_t __nmemb, size_t __size)
分配指定长度的存储区,且每一位都初始化为0
realloc
void *realloc (void *__ptr, size_t __size)
增加或减少之前的分配区,可能需要将之前分配的内容移动到另一个足够大的区域。新增区域的初始值不确定
以上三个分配函数的指针一定是适当对齐的,可适用于任何数据对象
free
void free (void *__ptr)
9.环境变量
getenv
char *getenv (const char *__name)
putenv
int putenv (char *__string)
形式为name=value的字符串,覆盖已有名称的name
setenv
int setenv (const char *__name, const char *__value, int __replace)
设置name和value,如果name存在,且replace非0,则替换现有定义
unsetenv
int unsetenv (const char *__name)
删除当前的name
环境表位于存储空间的顶部,下面就是栈区
修改name对于的value
- 如果长度小于等于原value的长度,则覆盖原value即可
- 如果长度大于原value的长度,则malloc后重新传递指针给name
- 没看明白
增加新的name
10. setjmp和longjmp
函数的调用栈
这俩函数用于实现函数栈帧之间的跳转
setjmp
- int _setjmp (struct __jmp_buf_tag __env[1])
? 返回0表示直接调用
? 返回非0,表示longjmp的跳转后的结果
- 将当前setjmp所在行的栈帧的信息存储在jmp_buf中
longjmp
- void longjmp (struct __jmp_buf_tag __env[1], int __val)
- 需要跳转的地方执行longjmp
? 第一个参数是setjmp缓存的jmpbuf
? 第二个参数是跳转到setjmp后,setjmp的返回结果
longjmp之后的问题
1.自动变量、寄存器变量、易失变量volatile
- longjmp之后,这些变量的值不确定
? 不进行任何优化时,自动变量、全局变量、静态变量、寄存器变量、volatile变量都不会改变
? 优化后,全局变量、static变量、volatile变量不会改变,而其他变量可以改变
- volatile关键字
? 1:编译器不知道多线程访问的变量特征,不知情哪些变量是在别的线程中修改的
? 2:程序运行过程中,变量会被存储到寄存器的值,而寄存器内的值不会被中断(外部改变变量)影响
? 3:编译器优化,通常,只知晓当前线程的操作,对变量的访问判断仅进行一次读取,后续访问均从寄存器获得。尽管外部线程对该变量进行了改写,当前线程依旧使用当前线程的寄存器(旧的值)
? 4:加上volatile关键字后,编译器不会再对该变量进行任何优化。访问时,会从变量地址处重新获取数据
? 举例:signal是外部变量,多线程共享
? 编译器优化的,在初始赋值后,ax不会再有任何改变
? 这是加上volatile关键字后,循环内部每次访问到ax,则重新读取
11.进程的资源限制
getrlimit
int getrlimit (__rlimit_resource_t __resource, struct rlimit *__rlimits)
setrlimit
int setrlimit (__rlimit_resource_t __resource, const struct rlimit *__rlimits)
资源限制,软限制和硬限制(软限制的最大值)