大纲
进程的终止
正常终止
- 从main函数返回
- 调用exit函数
用传入的值和0377(意味着只保留低8位,范围-128-127)相与,然后返回结果给父进程。
调用exit导致进程正常终止,使用atexit()或on_exit()注册的函数将会以逆序顺序调用 - 调用_exit或_Exit
_开头的是系统调用,被exit调用使用。普通用户调用exit函数,exit会调用终止处理程序(比如atexit)、标准IO清理程序等等,最后再调用_exit进行进程终止,而普通用户自己调用_exit将不会进行上面一些操作而直接终止 - 最后一个线程从其启动例程返回
- 最后一个线程调用pthread_exit
异常终止
- 调用abort
- 接到一个信号并终止
- 最后一个线程对其取消请求做出响应
命令行参数
getopt、getopt_long
int getopt(int argc, char * const argv[], const char *optstring);
//getopt专属的全局变量
extern char *optarg;
extern int optind, opterr, optopt;
前两个参数就是main的两个参数,而optstring是由选项字母组成的字符串。如果optstring中一个选项后面有冒号:,说明这个选项会有参数存在,并且这个参数由optarg指向。而optind表示下一个参数的下标,注意是下一个,不是当前这个。
返回值
If an option was successfully found, then getopt() returns the option character. If all command-line options have been parsed, then getopt() returns -1.
使用样例
实现一个有选项的mydate指令
/*
y输出年份 m输出月份 d输出日期
H输出小时 M输出分钟 S输出秒
*/
int main(int argc, char *argv[])
{
time_t stamp = time(NULL);
struct tm *tm = localtime(&stamp);
int c, index = 0;
char fmt[FMTSTRSIZE] = {0};
FILE *tmp = NULL;
FILE *fp[10];
while (1)
{
c = getopt(argc, argv, "-H:MSy:md");
//optstring的第一个字符是-,那么如果遇到非选项时
//返回的都是1
//同时这里的H和y都需要参数
if (c < 0)
break;
switch(c)
{
case 'y':
//如果参数是2则输出年份后两位
if (strcmp(optarg, "2") == 0)
strcat(fmt, "%y ");
//参数是4则输出4位
else if (strcmp(optarg, "4") == 0)
strcat(fmt, "%Y ");
else
fprintf(stderr, "Invalid argument %s", optarg);
break;
case 'm':
strcat(fmt, "%m ");
break;
case 'd':
strcat(fmt, "%d ");
break;
case 'H':
//参数12输出12小时制时间
if (strcmp(optarg, "12") == 0)
strcat(fmt, "%I(%P) ");
//参数24输出24小时制时间
else if (strcmp(optarg, "24") == 0)
strcat(fmt, "%H ");
else
fprintf(stderr, "Invalid argument %s", optarg);
break;
case 'M':
strcat(fmt, "%M ");
break;
case 'S':
strcat(fmt, "%S ");
break;
case 1:
//如果有选项是文件,则将日期输出到文件中
tmp = fopen(argv[optind - 1], "w");
if (tmp == NULL)
{
perror("fopen()");
tmp = stdout;
//不是文件则默认输出到stdout
}
fp[index++] = tmp;
break;
default:
break;
}
}
strcat(fmt, "\n");
char timestr[TIMESTRSIZE];
strftime(timestr, TIMESTRSIZE, fmt, tm);
for (int i = 0; i < index; i++)
{
fputs(timestr, fp[i]);
fclose(fp[i]);
}
exit(0);
}
还有个可以分析长格式的版本
int getopt_long(int argc, char * const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
环境变量
环境变量是一系列name=value形式的字符串。
环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录(HOME)、邮件存放位置(MAIL)等。例如Windows和DOS操作系统中的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。用户通过设置环境变量,来更好的运行进程。
getenv、setenv、putenv
char *getenv(const char *name);
传入一个name,返回一个value
int setenv(const char *name, const char *value, int overwrite);
用于改变或添加环境变量值。如果name不存在则添加,存在则是改变。如果overwrite是真则用现在的覆盖之前的,如果是假则保留原来的不进行覆盖
int putenv(char *string);
用于改变或添加环境变量值。与setenv的区别是传入的string是name=value的格式,如果name不存在则添加,否则修改成value
C程序的存储空间布局
- 正文段(.text)。这是由CPU执行的机器指令部分。通常正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本。正文段常常是只读的,以防止程序由于意外而修改其指令。
- 初始化数据段(.data)。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量(已经初始化的非零全局变量)。例如,C程序中任何函数之外的声明:
int maxcount = 99;
使此变量以其初值存放在初始化数据段中。 - 未初始化数据段(.bss)。通常将此段称为bss段,这一名称来源于时期汇编程序的一个操作符,意思是“由符号开始的块“(block started by symbol),在程序开始执行之前,(通常)内核将此段中的数据初始化为0或空指针。存放程序中未初始化的和零值全局变量。函数外的声明:
long sum[1000];
使此变量存放在非初始化数据段中。
text和data段都在可执行文件中,由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。 - 栈(stack)。按内存地址由高到低方向生长,其最大大小由编译时确定。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过心这种方式使用栈,C递归函数可以工作。递归函数每次调用自身时,就用一个新的栈帧,因此一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。
- 堆(heap)。*申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于未初始化数据段和栈之间。
库
静态库
动态库
共享库
函数跳转
假设要通过递归找寻一个数,一层一层的向下递归找到后其实不用再一层一层的返回。可以用类似于goto的操作直接返回的起点,可是goto不能跨函数,可以使用setjmp和longjmp
setjmp、longjmp
setjmp函数设置返回点,保存调用函数的栈环境于env中(相当于保护现场)。longjmp的作用是使用setjmp保存在env中的栈环境信息返回到setjmp的位置,也就是当执行longjmp时程序又回到setjmp处(相当于恢复现场)
int setjmp(jmp_buf env);
设置跳转点时返回的是0,如果是从longjmp跳回来的返回的就是非0。这是一种执行一次返回两次的函数
void longjmp(jmp_buf env, int val);
使用样例
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf sv;
static void a();
static void b();
static void c();
int main()
{
printf("%s():Begin\n", __FUNCTION__);
printf("%s():Call a()\n", __FUNCTION__);
a();
printf("%s():a() returned\n", __FUNCTION__);
printf("%s():End\n", __FUNCTION__);
exit(0);
}
static void a()
{
int ret;
printf("%s():Begin\n", __FUNCTION__);
ret = setjmp(sv);
if (ret == 0)
{
printf("%s():Call b()\n", __FUNCTION__);
b();
printf("%s():b() returned\n", __FUNCTION__);
}
else
{
printf("%s():Junmped back here with code %d\n", __FUNCTION__, ret);
}
printf("%s():End\n", __FUNCTION__);
}
static void b()
{
printf("%s():Begin\n", __FUNCTION__);
printf("%s():Call c()\n", __FUNCTION__);
c();
printf("%s():c() returned\n", __FUNCTION__);
printf("%s():End\n", __FUNCTION__);
}
static void c()
{
printf("%s():Begin\n", __FUNCTION__);
printf("%s():Jump now!\n", __FUNCTION__);
longjmp(sv, 6);
printf("%s():End\n", __FUNCTION__);
}
资源的获取及控制
学了这章可以实现自己的ulimit指令
getrlimit、setrlimit
获得、设置资源的限制
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
rlimit结构体如下:
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
这里分为硬限制和软限制。普通用户可以对软限制进行升高降低,但是不能高过硬限制;普通用户只能降低硬限制,不能提高。root可以升高、降低软限制,同样不能高过硬限制;root也可以升高、降低硬限制。