分类: LINUX
2008-12-09 13:37:01
启动
内核通过shell等界面,用fork(2)生成子进程,然后用exec(2)族函数执行所指定的程序;
内核调用专门的启动例程,取得命令行参数及环境变量;
开始从main开始执行程序;
创建进程的函数还包括system(3)和popen(3)等;
终止
终止程序有5种正常的终止方式和3种异常的终止方式:
正常的终止方式:
从main中返回(调用了return语句或者执行到结束处);
调用了exit(3)函数;
调用了_exit(3)或者_Exit(3)函数;
最后一个线程从启动例程返回;
最后一个线程调用了pthread_exit(2)函数;
异常的终止方式:
调用了abort(3)函数;
捕捉到一个信号并被其终止;
最后一个线程对pthread_cancel(2)取消请求作出了响应;
从main函数返回可以采用以下方式:
显示地调用return,main函数从return处返回;
让main函数一直执行到末尾自动退出;
以上两种情况main函数都将隐式的调用exit函数(以0为终止状态)执行后续处理,也可以直接调用exit函数并赋以终止状态作为参数:
#include
void exit(int status);
void _Exit(int status);
#include
void _exit(int status);
exit(3)在被调用时将首先执行相关的终止处理,可以用atexit(3)函数注册自定义的终止处理函数,exit(3)执行时将按最后注册最先执行的顺序依次执行它们:
#include
int atexit(void (*func)(void));
注意在使用atexit注册的终止处理函数无参数表和返回值;
_exit(2)和_Exit(2)是一样的,它们不会调用终止处理函数而是直接使进程终止;
关于进程的终止状态:
进程终止时将转为僵死状态,由父进程通过调用wait(2)收集其终止状态的方式在内核的进程表中解除注册。在bash shell下,可以通过命令"echoe $?"取得上一个程序执行结束后的终止状态;
如果进程从main函数的return语句返回,终止状态为main的返回值;
如果进程显式的从exit(3)、_exit(2)、_Exit(2)返回,终止状态为它们的参数;
如果进程结束时没有显式的调用返回语句,对于Linux,终止状态为所调用的最后一个有返回值的子函数的返回值;对于ISO C,main函数返回0。
若进程因捕捉到信号而终止,进程返回“128+信号值”;
没有遇到上述情况而从main函数处执行结束时,对于Linux,进程返回1;对于ISO C,main函数返回0;
显式的指定进程终止状态时常用两个宏常量表征进程执行成功或失败:EXIT_SUCCESS和EXIT_FAILURE;如:
if (buf == NULL) {
printf(“Buffer error!\n”);
exit(EXIT_FAILURE);
}
main主例程接受2~3个参数值,argc为命令行参数个数,argv为参数字符串的指针数组(参数表),如果需要,还可以加上程序的环境变量指针数组envp。参考K&R《The C Programming Language》(5.10 Command-line Arguments)。
环境变量表由静态的指针数组**environ(全局变量)维护,环境变量表的最后一个值为NULL;environ声明为:
#include
extern char **environ;
环境变量的名字字符串通常使用全大写字母。相关库函数:
#include
char *getenv(const char *name);
getenv根据给定的环境变量名字name返回其值,不存在时返回NULL;
#include
int putenv(char *str);
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
putenv使用形如“ENV_NAME=env_value”的字符串在environ中注册一个新的环境变量; setenv使用名字name在environ中注册一个环境变量,其值为value。若rewrite为真,在enrivon中已注册相同的变量名时将覆盖之,否则直接返回(但不属于出错)。
unsetenv从environ中解除变量name的注册。
新增或替换一个环境变量时:environ末尾的NULL将指向新变量,再在末尾添加一个NULL;
新增一个新的环境变量时,将导致environ由栈顶之上的地址空间移至堆地址空间中,以后environ将一直在堆中,但所保存的指针值不会因此被修改;
替换一个新的环境变量时,若新值的字符串比旧值要长,则会因realloc(3)而改变该值的字符串指针;
维护环境变量表的基本shell系统命令包括set(1)、unset(1)、env(1)、export(1)等;
以下程序从命令行中读取环境变量名字,并返回其值:
/*
* getenv.c:
* read an environment name from command line arguments
* and print its value
*/
#include "apue.h"
int main(int argc, char **argv)
{
char *envval;
if (argc < 2) {
printf("Usage: %s env\n", argv[0]);
exit(EXIT_FAILURE);
}
envval = getenv(argv[1]);
if (envval == NULL) {
printf("Unknown environment.\n");
exit(EXIT_FAILURE);
} else {
printf("%s", envval);
exit(EXIT_SUCCESS);
}
}
编译后,执行情况为:
mjxian@ubuntu ~/workspace/apuetest
$ ./getenv
Usage: ./getenv env
mjxian@ubuntu ~/workspace/apuetest
$ ./getenv VER
Unknown environment.
mjxian@ubuntu ~/workspace/apuetest
$ export VER=1.0
mjxian@ubuntu ~/workspace/apuetest
$ ./getenv VER
1.0
进程的地址空间是一个合法的虚拟内存映射地址,其最大值与处理器字长有关。进程可以合法访问的部分称为该进程的内存区域。
以x86为例,由低地址到高地址的进程内存区域依次包括:text程序正文段(只读的机器指令)、data数据段(已由C语句显式的初始化的全局变量)、bss数据段(未由C语句进行初始化的全局变量)、堆空间(向高地址增长)、栈空间(向低地址增长)、命令行参数和环境变量等。其中函数局部变量与非静态地址的函数在栈中分配地址,通过malloc()静态数据的分配在堆中进行。关于内存区域的更多细节,对于Linux可以参考《Linux Kernel Development》、《Understanding Linux Kernel》等书籍。
保存在磁盘上的程序文件,只包括text和data段。而堆、栈等需要程序执行才分配地址空间,bss分配的空间是固定的。命令size(1)可以查看程序文件存储时的地址分配。具体细节跟硬件平台的ELF规范及其实现有关。
关于堆地址空间
堆通常用于为程序通过malloc(3)、mmap(2)等函数动态分配所指定空间;malloc(3)的相关函数包括:
#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);
malloc根据所指定的size分配空间,成功时返回空间的首址,失败时返回NULL;
calloc分配nobj个size大小的连续空间,成功时将该段内存全部清零并返回其首址,失败时返回NULL;
realloc为已分配的ptr重新分配一块大小为newsize的空间,并返回其首址,失败时返回NULL;
free释放指定的ptr对应的地址空间。注意:ptr的值并不会因此变成NULL,free成功只说明了此段内存可以重新通过malloc获得,free后若再次直接访问ptr指向的地址是不安全的操作。通过malloc分配而不再使用的堆空间,应尽快通过free回收以避免内存泄漏。
另有在栈地址空间中进行分配的函数alloca(3);
使用malloc(3)相关函数时,应注意(的一种)、内存泄漏及重入性等问题。可参考我以前对于Linux内存管理相关函数记下的一些笔记。
关于栈地址空间
栈用于保存临时数据(局部变量、被调用的函数)。这些数据根据声明或调用的先后被依次压入栈中。函数setjmp和longjmp可用于在栈中进行定位。
#include
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp用于设置一个以env为标识的栈定位点;
longjmp用于跳转到env所定位的setjmp函数处,以类似C语句goto(但goto只能在函数中进行跳转)的方式继续执行程序,并以val作为setjmp的返回值。多个longjmp跳到一个setjmp处时,就可以通过返回的不同val值进行区别。
应注意的是,对于longjmp所到达栈中的局部临时变量和寄存器变量等,不能保证像时间机器一般可以回滚到当时的状态,而静态变量与volatile类型的变量则保持为最近一次被修改的值。特别的,对于局部自动变量,在生命期过后(函数返回被系统回收)不能被再次引用。
#include
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
结构rlimit定义资源对象的软限制rlim_cur和硬限制rlim_max。其中只有root进程可以增加硬限制,其它进程可以将限制改成rlim_max ≥ rlim_cur的值;
对应的系统命令为ulimit(1)。
书中未提及的还有getrusage(3)函数,它用于取得当前进程或其子进程的rusage结构:
#include
int getrusage(int who, struct rusage *usage);
who的取值包括RUSAGE_SELF和RUSAGE_CHILDREN。rusage记录了进程对于系统资源的使用情况,包括已执行的用户CPU时间和系统CPU时间、引起RAM访问的次数、引起磁盘交换分区访问的次数等。rusage结构在POSIX.1中只指定了ru_utime和ru_stime两个字段;
资源、环境变量等都影响到调用进程及其子进程;