相关函数列表
//退出函数 #includevoid exit(int status); void _Exit(int status); #include void _exit(int status); //按照ISO C的规定,一个进程可以登记多至32个函数,这些函数将由exit自动 //调用。我们称这些函数为终止处理程序(exit handler),并调用atexit函数来 //登记这些函数,这个函数起始就是注册退出钩子函数,退出程序时执行一些自定义的函数 //执行的顺序和注册的顺序正好相关,相同的函数可以注册多次 #include int atexit(void (*func)(void)); //环境表 int main(int argc, char *argv[], char *envp[]); //动态分配空间的函数 #include void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); void free(void *ptr); //获取环境变量值 #include char *getenv(const char *name); //putenv设置name=value //setenv将name设置为value,如果rewrite非0则先删除之前的定义否则不删 //unsetenv删除name定义 #include int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); //C语言中goto是不能跨越函数的,而执行这种功能是由setjmp和longjmp完成 #include int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); //每个进程都有一组资源限制,可以用下列函数设置和获取 #include int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struct rlimit *rlptr); //rlimit结构体如下 struct rlimit { rlim_t rlim_cur; //soft limit rlim_t rlim_max; //hard limit };
进程终止,有8种方式,其中5钟是正常终止
1) 从main返回
2) 调用exit
3) 调用_exit或_Exit
4) 最后一个现场从其启动列程返回
5) 从最后一个线程调用pthread_exit
异常终止
1) 调用abort
2) 接到一个信号
3) 最后一个线程对取消请求作出响应
1999 ISO C扩展要求编译器要求main必须声明为返回整型
当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行文件将此例程指定为程序的起始地址--这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后喂按上述方式调用main函数做好安排。
一个C程序是如何启动和终止的
函数sbrk()用来实现系统扩充(或缩小)进程的堆
但是大多数malloc和free的实现都不见效进程的存储空间,释放的空间可供以后再分配,但将它们保持在
malloc池中而不繁华给内核。
大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息--分配块的长度,指向
下一个分配块的指针等。这就意味着如果超过一个已分配区的尾端或者在已分配区起始位置之前进行写操作,则会改写另一块管理记录信息。这种类型的错误是在灾难性的单不会很快暴露出来。
替代的存储空间分配程序
1) libmalloc
2) vmalloc
3) quick-fit
4) jemalloc
5) TCMalloc
6) 函数alloca
Single UNIX Specification定义的环境变量
变量 | 说明 |
COLUMNS | 终端宽度 |
DATEMSK | getdate模板文件路径名 |
HOME | home起始目录 |
LANG | 本地名 |
LC_ALL | 本地名 |
LC_COLLATE | 本地排序名 |
LC_CTYPE | 本地字符分类名 |
LC_MESSAGES | 本地消息名 |
LC_MONETARY | 本地货币编辑名 |
LC_NUMERIC | 本地数字编辑名 |
LC_TIME | 本地日期/时间格式名 |
LINES | 终端高度 |
LOGNAME | 登陆名 |
MSGVERB | fmtmsg处理的消息组成部分 |
NLSPATH | 消息类模板序列 |
PATH | 搜索可执行文件的路径前缀列表 |
PWD | 当前工作目录的绝对路径名 |
SHELL | 用户首选的shell名 |
TERM | 终端类型 |
TMPDIR | 在其中创建临时文件的目录路径名 |
TZ | 时区信息 |
在更改资源限制时,必须遵守下列三条规则(使用ulimit命令获取和设置)
1) 任何一个进程都可将一个软限制值更改为小于或等于其硬限制值
2) 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值,这种降低对普通用户是不可逆的
3) 只有超级用户进程可以提高硬限制值
限制 | 说明 |
RLIMIT_AS | 进程总的可用存储空间的最大长度(字节),这影响到sbrk函数和mmap函数 |
RLIMIT_CORE | core文件的最大字节数,若其值为0则组织创建core文件 |
RLIMIT_CPU | CPU时间的最大量值(秒),当超过此软限制时,向该进程发送SIGXCPU信号 |
RLIMIT_DATA | 数据段的最大字节长度 |
RLIMIT_FSIZE | 可以创建的文件的最大字节长度,当超过此软限制时,则向该进程发送SIGXFSZ信号 |
RLIMIT_MEMLOCK | 一个进程使用mlock能够锁定在存储空间中的最大字节长度 |
RLIMIT_MSGQUEUE | 进程为POSIX消息队列可分配的最大存储字节数 |
RLIMIT_NICE | 为了影响进程的调度优先级,nice值可设置的最大限制 |
RLIMIT_NOFILE | 每个进程能打开的最多文件数,更改此限制将影响到sysconf函数在参数 _SC_OPEN_MAX中的返回值 |
RLIMIT_NPROC | 每个实际用户ID可拥有的最大子进程数,更改此限制将影响到sysconf函数在参数 _SC_CHILD_MAXZ中的返回值 |
RLIMIT_NPTS | 用户可用时打开的伪终端的最大数量 |
RLIMIT_RSS | 最大驻内存集字节长度(resident set size in bytes RSS)如果可用的物理存储器非常 少,则内核将从进程处取回超过RSS的部分 |
RLIMIT_SBSIZE | 在任意给定时刻,一个用户可以占用的套接字缓冲区的最大长度(字节) |
RLIMIT_SIGPENDING | 一个进程可排队的信号最大数量,这个限制是sigqueue函数实施的 |
RLIMIT_STACK | 栈的最大字节长度 |
RLIMIT_SWAP | 用户可消耗的交换空间的最大字节数 |
RLIMIT_VMEM | 这是RLIMIT_AS的同义词 |
RLIMIT_INFINITY | 指定了一个无限量的限制 |
atexit函数使用过程
#include#include #include static void my_exit1(void) { printf("first exit handler\n"); } static void my_exit2(void) { printf("second exit handler\n"); } int main(int argc, char *argv[]) { if(atexit(my_exit2) != 0) { printf("can't register my_exit2"); } if(atexit(my_exit1) != 0) { printf("can't register my_exit1"); } if(atexit(my_exit1) != 0) { printf("can't register my_exit1"); } printf("main is done\n"); return 0; }
gcc关闭共享库
gcc -static hello.c 关闭后可以发现目标文件明显比之前大了很多
假设一个函数程序如下
这个程序调用顺序是main --> do_line --> cmd_add --> get_token
如果在get_token()函数中发生了错误,函数需要跳回main中,此时我们不得不坚持返回值然后一个个返回
#include#include #include #define TOK_ADD 5 #define MAXLINE 1024 char *tok_ptr; int get_token(void) { //fetch next token from line pointed to by tok_ptr } void cmd_add(void) { int token; token = get_token(); //rest of processing for this command } void do_line(char *ptr) { int cmd; tok_ptr = ptr; while((cmd=get_token()) > 0) { switch(cmd) { case TOK_ADD: cmd_add(); break; } } } int main(int argc, char *argv[]) { char line[MAXLINE]; if(fgets(line,MAXLINE,stdin) != NULL) { do_line(line); } return 0; }
用setjmp和longjmp函数可以跳过函数返回
这里需要定义个全局的jmp_buf结构体,也就是setjmp()函数的env
longjmp可以对应多个setjmp()函数,通过第二个参数val区分,比如cmd_add对应1,而get_token对应2
这样就可以知道是哪个函数返回的了
#include#include #include #define TOK_ADD 5 #define MAXLINE 1024 char *tok_ptr; jmp_buf jmpbuffer; int get_token(void) { //fetch next token from line pointed to by tok_ptr } void cmd_add(void) { int token; token = get_token(); if(token < 0) { longjmp(jmpbuffer,1); } } void do_line(char *ptr) { int cmd; tok_ptr = ptr; while((cmd=get_token()) > 0) { switch(cmd) { case TOK_ADD: cmd_add(); break; } } } int main(int argc, char *argv[]) { char line[MAXLINE]; if(setjmp(jmpbuffer) != 0) { printf("setjmp error\r\n"); } if(fgets(line,MAXLINE,stdin) != NULL) { do_line(line); } return 0; }
执行跳转后各种类型的变量情况
自动,静态,寄存器,全局,易变 的变量是否会随着函数的回滚而回滚
目前测试是不会,所有变量都没有回滚,书中说到对于全部优化情况下,寄存器变量,易变变量会回滚
#include#include #include static jmp_buf jmpbuffer; static int globval; static void f2(void) { longjmp(jmpbuffer, 1); } static void f1(int i, int j, int k, int m) { printf("int f1()\n"); printf("globval=%d, atuoval=%d, regival=%d, valaval=%d, statval=%d\n",globval,i,j,k,m); } int main(int argc, char *argv[]) { int autoval; register int regival; volatile int volaval; static int statval; globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5; if(setjmp(jmpbuffer) != 0) { printf("after jmp\n"); printf("globval=%d, atuoval=%d, regival=%d, valaval=%d, statval=%d\n",globval,autoval,regival,volaval,statval); exit(0); } globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99; f1(autoval, regival, volaval, statval); exit(0); }
自动变量需要注意返回值
这里的函数open_data返回值的内容是当前函数栈中的,所以返回了就没有了,应当定义成静态的或者通过
malloc函数动态申请的
#include#include #include #include #include #define BUFSIZE 1024 FILE *open_data(void) { FILE *fp; char databuf[BUFSIZE]; if((fp=fopen("datafile","r")) == NULL) { return(NULL); } if(setvbuf(fp,databuf,_IOLBF,BUFSIZE) != 0) { return (NULL); } return fp; } int main(int argc, char *argv[]) { FILE *fp = open_data(); }
获取当前系统的各种软限制和硬限制
#include#include #define doit(name) pr_limits(#name, name); static void pr_limits(char *name, int resource) { struct rlimit limit; unsigned long long lim; if(getrlimit(resource, &limit) < 0) { printf("getrlimit error for %s,",name); } printf("%-14s ",name); if(limit.rlim_cur == RLIM_INFINITY) { printf(" (infinite) "); } else { printf("%10lld ",lim); } if(limit.rlim_max == RLIM_INFINITY) { printf(" (infinite) "); } else { printf("%10lld",lim); } putchar((int)'\n'); } int main(int argc, char *argv[]) { #ifdef RLIMIT_AS doit(RLIMIT_AS); #endif doit(RLIMIT_CORE); doit(RLIMIT_CPU); doit(RLIMIT_DATA); doit(RLIMIT_FSIZE); #ifdef RLIMIT_MEMLOCK doit(RLIMIT_MEMLOCK); #endif #ifdef RLIMIT_MSGQUEUE doit(RLIMIT_MSGQUEUE); #endif #ifdef RLIMIT_NICE doit(RLIMIT_NICE); #endif doit(RLIMIT_NOFILE); #ifdef RLIMIT_nproc doit(RLIMIT_NPROC); #endif #ifdef RLIMIT_RSS doit(RLIMIT_RSS); #endif #ifdef RLIMIT_SBSIZE doit(RLIMIT_SBSIZE); #endif #ifdef RLIMIT_SIGPENDING doit(RLIMIT_SIGPENDING); #endif doit(RLIMIT_STACK); #ifdef RLIMIT_SWAP doit(RLIMIT_SWAP); #endif #ifdef RLIMIT_VMEM doit(RLIMIT_VMEM); #endif return 0; }
参考
Linux下C程序进程地址空间布局
gcc 编译共享库
c程序是如何启动和终止