1、main函数执行是如何被调用?
内核在执行程序时,先调用一个特殊的启动例程,该例程被指定为程序的起始地址,并且从内核取得命令行参数和环境变量值。
2、进程终止
总共8种方式
正常终止:
1)从main返回
2)调用exit (先执行一些清理处理,然后返回内核)
3)调用_exit或_Exit (立即进入内核)
4)从最后一个线程从其启动例程返回
5)从最后一个线程调用pthread_exit
异常终止:
6)调用abort
7)接到一个信号
8)最后一个线程对取消请求做出响应
3、atexit函数
终止处理程序:在进程结束时,exit会自动调用一些函数(即终止处理程序),这些函数由进程使用atexit函数登记,多达32个。
#include <stdlib.h>
int atexit(void (*func)(void));
在程序结束时,exit调用终止处理程序的顺序和登记顺序相反,同一函数可以登记多次。
4、一个C程序是如何启动和终止?
exit首先会调用终止处理程序,然后关闭所有打开流,若程序调用exec函数族的任一函数,会清除所有已安装的终止处理程序。
内核使程序执行的唯一方法:调用一个exec函数
进程自愿终止的唯一方法:显示或隐式调用exit、_exit或_Exit
进程也可以非自愿由一个信号终止
5、命令行参数
int main(int argc, char* argv[])
argc:传入到程序的参数个数
argv:一个数组,里面依次存储着传入的参数,其中第一个是执行代码文件名
例如:
$ ./echoarg arg1 TEST foo
argc = 4
argv[0] = ./echoarg ;argv[1] = arg1 ;argv[2] = TEST; argv[3] = foo,argv[argc] = NULL
6、环境表
环境表和命令行参数类似,是main函数的第三个参数,存储着环境表的地址
int main(int argc, char* argv[], char *envp[]);
环境表是一个字符指针数组,每个指针包含一个以NULL结束的C字符串地址,其中全局变量environ保存该指针数组的地址
extern char **environ; //环境指针
envp和environ相比没有更多的益处,一般使用environ变量。使用getenv和putenv函数来访问特定的环境变量。
7、C程序的存储空间布局
存放在磁盘程序文件中的段只有正文段和初始化数据段
未初始化数据段的内容不在磁盘程序文件中,因为内核在程序运行前就将他们设置为0
C程序组成:
1)正文段 正文段可共享,且只读以防止程序由于意外而修改指令
2)初始化数据段 通常称数据段;任何函数之外的变量声明
3)未初始化数据段 在定义变量时未初始化
4)栈 自动变量、每次函数调用需要保存的信息都存放在此段中。递归函数每次调用自身,就用一个新的栈帧。
5)堆 在堆中进行动态存储分配
8、共享库
优点:
1)共享库使得可执行文件中不再需要包含公用的库函数,只需在所有进程都可引用的存储区中保存这种库例程的一个副本,可以减少每个执行文件的长度。
2)可以用库函数的新版本代替老版本,无需对使用库的程序进行重新连接编辑
缺点:增加了一些运行时间开销(发生在程序第一次执行或者共享库函数 第一次被调用的时候)
9、存储空间布局
三个用于存储空间动态分配的函数:
这三个函数返回的指针一定是适当对齐的。
大多数分配的存储空间比所要求的要大些,需要额外的空间来记录管理信息——分配块的长度、指向下一个分配块的指针等。超过一个分配区的尾端或在已分配区起始位置执行写操作,会改写另一块的管理记录信息。这种错误不会很快暴露,很难发现。
1)void *malloc(size_t size);
分配一块size长度的存储区,初始值不确定。
2)void *calloc(size_t nobj, size_t size);
分配nobj个size长度的存储区,初始化为0
3)void *realloc(void *ptr, size_t newsize);
重新分配newsize大小的存储区。扩大ptr所指的地址,如果空间不够,就重新开辟一块空间将原来的数据拷贝过去,新分配部分不会初始化,旧的存储区域会自动释放。newsize小于原来的大小,可能导致数据丢失。
注意:realloc可能会使内存地址发生变化,因此要避免任何指向该地址的指针
10、环境变量
1)获取环境 变量值
#include <stdlib.h>
char *getenv(const char *name);
2)如何修改环境表?
环境表和环境字符串通常存放在进程存储空间的顶部。
删除一个字符串很简单,只需找到该指针后然后将后续指针都向环境表首部顺次移动一个位置。
但是增加或者修改字符串就很困难 ,由于其占用进程地址空间的顶部,下面是栈(不能移动栈帧),既不能低地址(下面是栈)扩展,也不能向高地址扩展(已经在进程地址空间的顶部)。
修改name:
新的value长度小于等于现有value,将新的字符串复制到原空间即可
新的value长度大于的话,必须调用malloc为新的字符串分配空间,然后将字符串复制到该空间且环境表的指针也要指向新分配区
增加name:必须调用malloc分配空间
第一次增加name,要调用malloc为新的指针表分配空间,将原来的环境表复制到新分配区,新的name放在表尾,在最后面加一个空指针;让environ指向新指针表。注意:环境表移至堆中了,但是原大多数指针所指的字符串仍然在栈顶,新增加的字符串在堆中
不是第一次增减name,需要调用malloc为环境表在堆中分配空间,调用realloc分配比原空间多存放一个指针空间,将name字符串指针存放在表尾,后面跟着一个空指针。注意:此时增加的字符串是在堆中
***********************************************************
11、函数setjmp和longjmp
跳转功能,函数setjmp和longjmp,非局部
goto语句不能跨越函数,
——没有看懂???
12、函数getrlimit和setrlimit
使用getrlimit和setrlimit函数查询和更改资源限制
资源限制通常是在系统初始化时由0进程建立的,由后续进程继承并作出修改调整。
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
struct rlimit{
rlimit_t rlim_cur //soft limit
rlimit_t rlim_max; //hard limit
};
软限制:用户在一定时间范围超过软限制的额度,达到时间期限就不允许了
硬限制:在任何情况下都不允许用户超过这个限制
更改资源限制的三条规则:
1)任何一个进程可以将软限制值修改成小于等于硬限制值
2)任何一个进程可以降低硬限制值但必须大于等于软限制值;对普通用户不可逆
3)只有超级用户进程才可以提高硬限制值