本文章主要来自于李慧琴老师的课程,参考书籍:unix环境高级编程
main函数
int main(int argc ,char** argv)
进程的终止
正常终止
1、从main函数返回
return 0;
// 给父进程看的
2、调用exit
exit()
stdlib.h: void exit(int status);
//参 数 : 整型数 status 程序退出的返回值.
//作用:关闭所有文件,终止正在执行的进程。
//exit(1)表示进程正常退出. 返回 1;
//exit(0)表示进程非正常退出. 返回 0.
//exit()的参数会被传递给一些操作系统,包括UNIX,Linux,和MS //DOS,以供其他程序使用。
3、调用_exit 或_EXIT
4、最后一个线程从其启动例程返回
5、最后一个线程调用pthread_exit
异常终止
1、调用了abort
2、接到一个信号并终止
3、最后一个进程对其取消请求作出响应
钩子函数atexit()
按照 ISO C 的规定,一个进程可以登记多至32个函数,这些函数将由 exit 自动调用,我们称这些函数为 终止处理程序 (exit handler) ,并调用atexit函数来登记这些函数
man atexit()
NAME
atexit - register a function to be called at normal process termination
//在进程正常终止(五种情况)的时候这个函数被调用
SYNOPSIS
#include <stdlib.h>
int atexit(void (*function)(void));
其中,此函数参数是一个函数地址,当调用此函数时无需向他传递任何参数,也不期望它返回一个值。exit调用这些函数的顺序与他们的登记时的顺序相反。同一函数如果被登记多次,同样也会被调用多次
DESCRIPTION
The atexit() function registers the given function to be called at normal process termination, either via exit(3) or via return from the program's
main().
//atexit()函数注册在正常进程终止时调用的给定函数,可以通过exit(3),也可以通过程序的return
Functions so registered are called in the reverse order of their registration; no arguments are passed.
//这样注册的函数的调用顺序与它们的注册顺序相反;不传递参数。
The same function may be registered multiple times: it is called once for each registration.
//相同的函数可以被调用多次
POSIX.1 requires that an implementation allow at least ATEXIT_MAX (32) such functions to be registered. The actual limit supported by an implementation can be obtained using sysconf(3).
When a child process is created via fork(2), it inherits copies of its parent's registrations. Upon a successful call to one of the exec(3) functions, all registrations are removed.
应用实例
#include<stdio.h>
#include<stdlib.h>
static void f1(void)
{
puts("f1() is working!");
}
static void f2(void)
{
puts("f2() is working!");
}
static void f3(void)
{
puts("f3() is working!");
}
int main()
{
puts("Begin!\n");
atexit(f1);
atexit(f2);
atexit(f3);
//这三句话不是函数调动,是把他们挂到钩子上,直到exit执行前逆序输出
exit(0);
}
输出结果
Begin!
f3() is working!
f2() is working!
f1() is working!//逆序输出
命令行参数的分析
当传递一个程序时,调用 exec 的进程可将命令行参数传递给新程序
getopt()
getopt_long();
#include <unistd.h>
#include <getopt.h>
int getopt(int argc,char const **argv, const char *optstring);
int getopt_long(int argc,char const **argc,
const char *optstring,const struct option *longopts,
int *longindex);
extern char *optarg;
extern int optind,opterr,optopt;
struct option {
char *name;
int has_flag;
int *flag;
int value;
};
RETURN VALUE
If an option was successfully found, then getopt() returns the option character. If all commanline options have been parsed, then getopt() returns -1.
二者的区别
getopt_long是getopt的扩展.getopt接受的命令行参数只可以是以(-)开头,而getopt_long还可以接受(--)开头的参数.一般以(-)开头的参数的标志只有一个字母,而以(--)开头的参数可以是一个字符串.如上面的 --d,--option1选项.
argc,和argv参数是main函数的参数.optstring指出了我们可以接受的参数.其一般的形式为:参数1[:]参数2[:]....
其中参数是我们可以接受的参数,如果后面的冒号没有省略,那么表示这个参数出现时后面必需要带参数值. 比如一个optstring为abc:d:表示这个参数选项可以为a,b,c,d其中c,d出现时候必须要有参数值.如果我们输入了一个我们没有提供的参数选项.系统将会说 不认识的选项.
getopt返回我们指定的参数选项.同时将参数值保存在optarg中,如果已经分析完成所有的参数函数返回-1.这个时候optind指出非可选参数的开始位置.
实例分析
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int is_a,is_b,is_c,is_d,i;
char *a_value,*b_value,*c_value,temp;
is_a=is_b=is_c=is_d=0;
a_value=b_value=c_value=NULL;
if(argc==1)
{
fprintf(stderr,"Usage:%s [-a value] [-b value] [-c value] [-d] arglist .../n",argv[0]);
exit(1);
}
while((temp=getopt(argc,argv,"a:b:c:d"))!=-1)
{
switch (temp)
{
case ''a'':
is_a=1;
a_value=optarg;
break;
case ''b'':
is_b=1;
b_value=optarg;
break;
case ''c'':
is_c=1;
c_value=optarg;
break;
case ''d'':
is_d=1;
break;
}
}
printf("Option has a:%s with value:%s/n",is_a?"YES":"NO",a_value);
printf("Option has b:%s with value:%s/n",is_b?"YES":"NO",b_value);
printf("Option has c:%s with value:%s/n",is_c?"YES":"NO",c_value);
printf("OPtion has d:%s/n",is_d?"YES":"NO");
i=optind;
while(argv[i])
printf(" with arg:%s/n",argv[i++]);
exit(0);
}
环境表
每一个程序都接收到一张环境表,与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以 null 结束的 C 字符串的地址。全局变量environ则包含了该指针数组的地址,如下图所示:
环境变量
KEY = VALUE
一般是指操作系统中用来指定操作系统运行环境的一些参数。通常具有全局性。
如:我们在编写C/C++代码的时候,再链接的时候,我们从来不知道所链接的动态静态库
哪,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
在历史上,大多数UNIX系统支持main函数带第三个参数,其中第三个参数就是环境表地址:
int main(int argc,char ** argv,char *envp[]);
因为ISO C规定main函数只有两个参数,而且第三个参数与全局变量environ相比也没有带来更多的益处,所以POSIX.1也规定使用environ而不使用第三个参数,通常用getenv和putenv函数来访问特定的环境变量,而不是使用environ变量。但是,如果要查看整个环境,则必须使用environ指针
getenv()
NAME
getenv, secure_getenv - get an environment variable
SYNOPSIS
#include <stdlib.h>
char *getenv(const char *name);
setenv()
NAME
setenv - change or add an environment variable
//change:第一个参数 name 存在,add: name不存在
SYNOPSIS
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
setenv将 name 设置为value,如果在环境中name已经存在,那么overwrite : 如果为真,用现在的去覆盖原来的,为假,原来的不变
int unsetenv(const char *name); // 取消环境变量
删除name定义,即使不存在这种定义也不算出错
putenv()
NAME
putenv - change or add an environment variable
取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义。
SYNOPSIS
#include <stdlib.h>
int putenv(char *string);
库
动态库
1)以libxxx.so命名
2)在可执行程序编译的时候,不会链接动态库,而是在程序执行的时候,将动态库加载到内存中。
因此,可执行程序比较小。
3)占用内存和磁盘空间比较少
4)程序更新部署比较方便
5)编译后的可执行程序,脱离动态库后,会崩溃
静态库
1)以libxxx.a命名
2)在可执行程序编译的时候,直接将静态库结成到可执行程序中,因此可执行程序比较大。
3)可执行程序在运行的时候占内存比较大。存储的时候,占磁盘空间比较多。
4)给程序更新部署带来麻烦
5)编译后的可执行程序,脱离静态库后不受影响
C程序的存储空间布局
补充:
堆顶和栈顶之间存在很大的虚地址空间
函数跳转
在C中,goto语句是不能跨越函数的,而执行这种类型的跳转功能的是函数setjmp 和 longjmp。这两个函数对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的。
#include <setjmp.h>
int setjmp(jmp_buf env); //直接调用则返回0,如从longjmp调用则返回非0
int longjmp(jmp_buf env, int val);
jum_buf是一个类型,其中env存储了一些longjmp调用返回用来恢复栈状态的所有信息。longjmp中的env和setjmp中的 env是同一个。val是程序员自定义的直,这个值用在setjmp的返回值中,这样我们就可以知道是在哪个的longjmp跳转回来的。另外,由于在不 同的函数间调用setjmp和longjmp,而这两个函数要公用一个env变量,所以把env定义为一个全局变量
资源的获取与控制
每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit
#include<sys/resource.h>
int getrlimit(int resource,struct rlimit *rlptr);
int setrlimit(int resource,const struct rlimit *rlptr);
这两个函数在single UNIX Specification 的XSL扩展中定义。进程的资源限制通常是在系统初始化的时候有0 进程创立的,然后由后续进程继承。美中实现都可以用自己的方法对资源限制作出调整。
对两个函数的每一次调用都指定一个资源以及一个指向下列结构的指针
struct rlimit
{
rlim_t rlim_cur;
rlim_t rlim_max;
}