系统和时间
索引:
- 命令行处理函数getopt和getsubopt
- 动态库的动态加载
- 出错函数strerror和perror
- 系统限制
- 一些基本数据类型
- 存取口令文件信息的函数
- 存取组文件信息的函数
- 存取和设置添加组ID函数
- 登录会计文件
- 系统标识函数uname
- 精灵进程(daemon)
- syslog机制
- 日历时间和进程时间
- 时间和日期函数
1.命令行处理函数getopt和getsubopt
#include <unistd.h> extern char *optarg; extern int optind; /* initialized to 1 */ extern int optopt; extern int opterr; /* initialized to 1 */ extern int optreset; /* extension to IEEE Std1003.2 “POSIX.2” */ extern void getoptreset(void); /* SGI IRIX 6.5 only */ int getopt( int argc, char * const *argv, const char *optstring );
返回:成功时返回被解析的选项字母;当到达选项结尾时,返回-1;当遇到不可识别的选项字母时,返回值'?';如果参数optstring以一个':'字符开始,则当要求一个参数的选项没有给定参数时,就返回':'。
参数optstring告诉getopt支持哪些选项以及哪些选项带有参数,在带参数的选项字母后加':'。如tar -cvf prog.tar,optstring为"cvf:",在optstring前加':',当解析了有效选项但没有找到跟随它的参数时,getopt返回':',此时可以为选项参数假定一些其他默认值。
外部变量的含义:
- optarg:被设置成指向为要处理的选项所提供的参数,仅对有参数的选项这么做。如-f proger.tar,当getopt返回时,optarg指向包含proger.tar的C串。
- optind:该变量初始设置为1,getopt使用它指向要处理的下一个argv[]值。
- opterr:初始值为1(表示真),用做getopt的输入,当它为真且遇到不可识别的选项字符时,getopt打印错误信息到stderr。当opterr被设置为0(假)时,这种特性被取消。当程序进行错误报告或错误信息必须送到不是stderr的其他地方时,这是必要的。
许多UNIX平台支持子选项。如mount -o rw,hard,bg,wsize=1024 speed:/usr /usr,这里rw,hard,bg,wsize=1024是-o选项的子选项。处理子选项使用getsubopt函数:
#include <stdlib.h> extern char *suboptarg; int getsubopt(char **subopts_str, char * const *tokens, char **valuep );
返回:如果值是一个可识别的选项,返回值是token[]数组的下标,参数valuep返回的指针将包含一个指针,它指向子选项subopt=value的值的部分,若没有提供任何值,valuep包含空指针;当子选项没有被识别为token[]数组中的选项时,返回值-1。
subopt_str是一个指针,指向要解析的串,这个指针是由函数的每次调用来更新的。当没有剩下子选项时,该指针将指向串中的一个空字节。
参数token是标记串指针的数组,这些指针代表有效的子选项值,数组的最后一个元素应是一个空指针,标志数组的结束。如:
static char *tokens[] = { “rw”, “hard”, “bg”, “wsize”, NULL };
参数valuep是一个指向字符指针的指针。在getsubopt调用返回它所指向的指针后,若这个参数没有值,它将是空的,否则它指向值串。
使用举例:
extern char *optarg; /* getopt */ char *valuep; int x; while ( *optarg != 0 ) switch ( ( x= getsubopt( &optarg, tokens, &valuep ) ) ) { case 3: /* wsize=arg */ printf( “%s=%s\n”, tokens[x], valuep ? valuep:”NULL” ); … …
2.动态库的动态加载
动态库也可以在运行时动态链接。使用如下方法实现(不是所有的UNIX系统都支持):
(1).打开动态库
#include <dlfcn.h> void *dlopen(const char *path, int mode); 返回:成功时为共享库的句柄,出错为NULL
参数path指定共享库的名称,参数mode指定引用方式,它的值为RTLD_LAZY或RTLD_NOW:
- RTLD_LAZY:自动处理共享库的依赖性问题。如加载一个共享库时,它可能需要调用另一个共享库(该库可能已加载,也可能未加载)。
- RTLD_NOW:如果共享库需要调用别的共享库,则在共享库开始执行之前处理这个引用。在没有找到或不能加载其他共享库的情况下,如果不想让程序继续执行,则采用这种方式。该方式由于能立即处理所有动态符号,所以执行速度要快。
注意:共享库要存放在标准目录下,或在LD_LIBRARY_PATH变量中列出的目录下。
#include <dlfcn.h> const char *dlerror(void);
当dlopen失败时,使用dlerror函数可以获得一个有意义的错误消息。dlerror返回一个字符串指针,指向的内容在下一次调用某个动态库函数之前,始终保持有效。
(2).获得共享的引用指针
#include <dlfcn.h> void *dlsym(void *handle, const char *symbol); 返回:成功为相应引用的指针,出错时为NULL
参数handle是dlopen函数的返回值,symbol是一个字符串,它包含你感兴趣的函数名或外部数据结构名。
如果共享库中不存在要找的符号,则返回一个空指针。
使用举例:
void *dlh; int (*f)(const char *format,..); f = (int (*)(const char *,…)) dlsym(dlh,”printf” ); f(“The dlsym call worked!\n” ); /* call printf() now */
(3).关闭共享库
#include <dlfcn.h> int dlclose(void *handle);
参数handle是dlopen函数的返回值。
dlopen和dlclose函数都保留引用计数,当引用计数降为0,会卸载共享库并释放相应资源。
(4).初始化和析构
当dlopen函数第一次加载共享库时,会调用_init()符号(如果它存在的话)。当卸载共享库时,会调用_fini()符号(如果它存在的话)。函数原型如下:
void _init(void); /* called by dlopen */ void _fini(void); /* called by dlclose */
这种机制允许共享库的初始化和清除操作。
(5).其他平台函数
在有的UNIX平台下提供不同的API来实现相似的功能。如在HPUX10.2下:
- shl_load,对应dlopen;
- shl_findsym,对应dlsym;
- shl_unload,对应dlclose。
3.出错函数strerror和perror
#include <string.h> char *strerror(int errnum);
将errnum映射为一个出错信息字符串,并且返回此字符串的指针。
void perror(const char *msg);
首先输出msg指向的字符串,然后是一个冒号,一个空格,之后是对应于errno值的出错信息,然后是一个新行符。
4.系统限制
UNIX存在多种标准和实现,从而有不同的限制。要获得一个特定系统实际支持的限制值,使用如下函数:
#include <unistd.h> long sysconf(int name); log pathconf(const char *pathname,int name); log fpathconf(int filedes,int name); 所有函数返回:若成功为相应值,若出错为-1
name参数的取值如下表所示,以_SC_开始的常数作为sysconf的参数,以_PC_开始的参数作为pathconf和fpathconf的参数:
限制名 | 说明 | name参数 |
---|---|---|
ARG_MAX | exec函数的参数最大长度(字节) | _SC_ARG_MAX |
CHILD_MAX | 每个实际用户ID的最大进程数 | _SC_CHILD_MAX |
clock ticks/second | 每秒时钟滴答数 | _SC_CLK_TCK |
NGROUPS_MAX | 每个进程的最大同时添加组ID数 | _SC_NGROUPS_MAX |
OPEN_MAX | 每个进程最大打开文件数 | _SC_OPEN_MAX |
PASS_MAX | 口令中的最大有效字符数 | _SC_PASS_MAX |
STREAM_MAX | 在任一时刻每个进程的最大标准I/O流数――如若定义,则其值一定与FOPEN_MAX相同 | _SC_STREAM_MAX |
TZNAME_MAX | 时区名中的最大字节数 | _SC_TZNAME_MAX |
_POSIX_JOB_CONTROL | 指明实现是否支持作业控制 | _SC_JOB_CONTROL |
_POSIX_SAVED_IDS | 指明实现是否支持保存的设置-用户-ID和保存的设置-组-ID | _SC_SAVED_IDS |
_POSIX_VERSION | 指明POSIX.1版本 | _SC_VERSION |
_XOPEN_VERSION | 指明XPG版本(非POSIX.1) | _SC_XOPEN_VERSION |
LINK_MAX | 文件连接数的最大值 | _PC_LINK_MAX |
MAX_CANON | 在一终端规范输入队列的最大字节数 | _PC_MAX_CANON |
MAX_INPUT | 终端输入队列可用空间的字节数 | _PC_MAX_INPUT |
NAME_MAX | 文件名中的最大字节数(不包括null结束符) | _PC_NAME_MAX |
PATH_MAX | 相对路径名中的最大字节数(不包括null结束符) | _PC_PATH_MAX |
PIPE_BUF | 能原子地写到一管道的最大字节数 | _PC_PIPE_BUF |
_POSIX_CHOWN_RESTRICTED | 指明使用chown是否受到限制 | _PC_CHOWN_RESTRICTED |
_POSIX_NO_TRUNC | 指明若路径名长于NAME_MAX是否产生一错误 | _PC_NO_TRUNC |
_POSIX_VDISABLE | 若定义,终端专用字符可用此值禁止 | _PC_VDISABLE |
对于pathconf的参数pathname,fpathconf的参数filedes有很多限制。如果不满足其中任何一个限制,则结果是未定义的:
- _PC_MAX_CANON、_PC_MAX_INPUT以及_PC_VDISABLE所涉及的文件必须是终端文件。
- _PC_LINK_MAX所涉及的文件可以是文件或目录。如果是目录,则返回值用于目录本身(不用于目录内的文件名项)。
- _PC_NAME_MAX和_PC_NO_TRUNC所涉及的文件必须是目录,返回值用于该目录中的文件名。
- _PC_PATH_MAX涉及的必须是目录。当所指定的目录是工作目录时,返回值是相对路径名的最大长度。(不幸的是,这不是我们想要知道的一个绝对路径名的实际最大长度)
- _PC_PIPE_BUF所涉及的文件必须是管道、FIFO或目录。在管道或FIFO情况下,返回值是对所涉及的管道或FIFO的限制值。对于目录,返回值是对在该目录中创建的任一FIFO的限制值。
- _PC_CHOWN_RESTRICTED必须是文件或目录。如果是目录,则返回值指明此选择项是否适用于该目录中的文件。
返回值的详细说明:
- 如果name不是上表中的一个合适的常数,则所有这三个函数都返回-1,并将error设置为EINVAL。
- 包含MAX的12个name以及name_PC_PIPE_BUF可能或者返回该变量的值(返回值≥O),或者返回-1,这表示该值是不确定的,此时并不更改errno的值。
- 对_SC_CLK_TCK的返回值是每秒的时钟滴答数,以用于times函数的返回值。
- 对_SC_VERSION的返回值以4位数和2位数分别表示此标准的年和月。这可能或者是198808L或199009L,或此标准某个以后版本的值。
- 对_SC_XOPEN_VERSION的返回值表示此系统所遵从的XPG版本,其当前值是3。
- _SC_JOB_CONTROL和_SC_SAVED_IDS是两个可选功能。若sysconf返回-1(没有更改errno)则不支持相应的功能。这两个功能也可在编译时从unistd.h头文件中决定。
- 对_PC_CHOWN_RESTRICTED和_PC_NO_TRUNC的返回值若为-1(不改变errno),则表示对所指定的pathname或filedes不支持此功能。
- 对_PC_VDISABLE的返回值若为-1(不改变errno),则表示对所指定的pathname或filedes不支持此功能。若支持此功能,则返回值是被用于禁止特定终端输入字符的字符值。
5.一些基本数据类型
位于头文件<sys/types.h>中,以下是其中一部分:
类型 | 说明 |
---|---|
caddr_t | 内存地址 |
clock_t | 时钟滴答计数器(进程时间) |
comp_t | 压缩的时钟滴答 |
dev_t | 设备号(主和次) |
fd_set | 文件描述符集 |
fpos_t | 文件位置 |
gid_t | 数值组ID |
ino_t | 节点编号 |
mode_t | 文件类型,文件创建方式 |
n1ink_t | 目录项的连接计数 |
off_t | 文件长度和位移量(带符号的) |
pid_t | 进程ID和进程组ID(带符号的) |
ptrdiff_t | 两个指针相减的结果(带符号的) |
r1im_t | 资源限制 |
sig_atomic_t | 能原子地存取的数据类型 |
sigset_t | 信号集 |
size_t | 对象(例如字符串)长度(不带符号的) |
ssize_t | 返回字节计数的函数(带符号的) |
time_t | 日历时间的秒计数器 |
uid_t | 数值用户ID |
wchar_t | 能表示所有不同的字符码 |
6.存取口令文件信息的函数
#include <sys/types.h> #include <pwd.h> struct passwd *getpwuid(uid_t uid); struct passwd *getpwnam(const char *name); 两个函数返回:若成功则为指针,若出错则为NULL #include <sys/types.h> #include <pwd.h> struct passwd *getpwent(void); 返回:若成功则为指针,若出错或到达文件尾端则为NULL void setpwent(void); void endpwent(void);
调用getpwent时,它返回口令文件中的下一个记录。
函数setpwent反绕它所使用的文件。
endpwent则关闭这些文件。在使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。getpwent知道什么时间它应当打开它所使用的文件(第一次被调用时),但是它并不能知道何时关闭这些文件。
7.存取组文件信息的函数
#include <sys/types.h> #include <grp.h> struct group *getgrgid(gid_t gid); struct group *getgrnam(const char *name); 两个函数返回:若成功则为指针,若出错则为NULL #include <sys/types.h> #include <grp.h> struct group *getgrent(void); void setgrent(void); void endgrent(void);
8.存取和设置添加组ID函数
#include <sys/types.h> #include <unistd.h> int getgroups(int gidsetsize, gid_t grouplist[]); 返回:若成功则为添加的组ID数,若出错则为-1 int setgroups(int ngroups, const gid_t grouplist[]); int initgroups(const char *username, gid_t basegid); 两个函数返回:若成功则为0,若出错则为-1
getgroups将进程所属用户的各添加组ID填写到数组grouplist中,填写入该数组的添加组ID数最多为gidsetsize个。实际填写到数组中的添加组ID数由函数返回。如果系统常数NGROUPS_MAX为0,则返回0,这并不表示出错。作为一种特殊情况,如若gidsetsize为0,则函数只返回添加组ID数,而对数组grouplist则不作修改。(这使调用者可以确定grouplist数组的长度,以便进行分配。)
setgroups可由超级用户调用以便为调用进程设置添加组ID表。grouplist是组ID数组,而ngroups说明了数组中的元素数。通常,只有initgroups函数调用setgroups。
9.登录会计文件
大多数UNIX系统都提供下列两个数据文件:
- utmp文件,它记录当前登录进系统的各个用户;
- wtmp文件,它跟踪各个登录和注销事件。
登录时,login程序填写这样一个结构,然后将其写入到utmp文件中,同时也将其添写到wtmp文件中。注销时,init进程将utmp文件中相应的记录擦除(每个字节都填以0),并将一个新记录添写到wtmp文件中。
10.系统标识函数uname
#include <sys/utsname.h> int uname(struct utsname * name); 返回:若成功则为非负值,若出错则为-1
通过该函数的参数向其传递一个utsname结构的地址,然后该函数填写此结构,它返回与主机和操作系统有关的信息。
伯克利类的版本提供gethostname函数,它只返回主机名,该名字通常就是TCP/IP网络上主机的名字:
#include <unistd.h> int gethostname(char *name, int namelen); 返回:若成功则为0,若出错则为-1
11.精灵进程(daemon)
精灵进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。
编写一个精灵进程,有如下步骤:
- 首先做的是调用fork,然后使父进程exit。这样做实现了下面几点:第一、如果该精灵进程是由一条简单shell命令起动的,那么使父进程终止使得shell认为这条命令已经执行完成。第二、子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的setsid调用是必要的前提条件。
- 调用setsid以创建一个新对话期。使调用进程:(a)成为新对话期的首进程,(b)成为一个新进程组的首进程,(c)没有控制终端。在SVR之下,有些人建议在此时再调用fork,并使父进程终止。第二个子进程作为精灵进程继续运行。这样就保证了该精灵进程不是对话期首进程,于是按照SVR4规则,可以防止它取得控制终端。另一方面,为了避免取得控制终端,无论何时打开一个终断设备都要指定O_NOCTTY。
- 将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。
- 将文件方式创建屏蔽字设置为0。
- 关闭不再需要的文件描述符。
12.syslog机制
因为精灵进程通常没有控制终端。在SVR4下,可以使用流记录驱动程序,在4.3+BSD之下,提供了syslog设施。不过SVR4也提供BSD syslog设施。
syslog设施使用如下函数:
#include <syslog.h> void openlog(char *ident, int option, int facility); void syslog(int priority , char *format, ...); void closelog(void);
调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslogd精灵进程通信的描述符。
调用openlog使我们可以指定一个ident,以后,此ident将被加至每则记录消息中。ident一般是程序的名称。
有4种可能的option:
- LOG_CONS:若日志消息,不能通过UNIX域数据报发送至syslogd,则将该消息写至控制台;
- LOG_NDELAY1:立即打开UNIX域数据报套接口至syslogd精灵进程,不要等到记录第一条消息。通常,在记录第一条消息之前,该套接口不打开;
- LOG_PERROR:除将日志消息发送给syslog外,还将它写至标准出错。此选项仅由4.3BSD Reno及以后版本支持;
- LOG_PID:每条消息都包含进程ID,此选择项可供对每个请求都fork一个子进程的精灵进程使用。
facility可以选取如下的值。设置facility参数的目的是让配置文件可以说明,来自不同设施的消息以不同的方式进行处理。如果不调用openlog,或者以facility为0来调用它,那么在调用syslog时,可将facility作为priority参数的一个部分进行说明:
- LOG_AUTH:授权程序:login,su,,getty,…
- LOG_CRON:cron和at
- LOG_DAEMON:系统精灵进程:ftpd, routed,…
- LOG_KERN:内核产生的消息
- LOG_LOCAL0:保留由本地使用
- LOG_LOCAL1:保留由本地使用
- LOG_LOCAL2:保留由本地使用
- LOG_LOCAL3:保留由本地使用
- LOG_LOCAL4:保留由本地使用
- LOG_LOCAL5:保留由本地使用
- LOG_LOCAL6:保留由本地使用
- LOG_LOCAL7:保留由本地使用
- LOG_LPR:行打系统:lpd,lpc,…
- LOG_MAIL:邮件系统
- LOG_NEWS:Usenet网络新闻系统
- LOG_SYSLOG:syslogd精灵进程本身
- LOG_USER:来自其他用户进程的消息
- LOG_UUCP:UUCP系统
调用syslog函数产生一个记录消息。其priority参数是facility和level的组合,level值按优先级从最高到最低按序排列如下:
- LOG_EMERG:紧急(系统不可使用)(最高优先级)
- LOG_ALERT:必须立即修复的条件
- LOG_CRIT:临界条件(例如,硬设备出错)
- LOG_ERR:出错条件
- LOG_WARNING:警告条件
- LOG_NOTICE:正常,但重要的条件
- LOG_INFO:信息性消息
- LOG_DEBUG:调试排错消息(最低优先级)
format参数以及其他参数传至vsprintf函数以便进行格式化。在format中,每个%m都被代换成对应于errno值的出错消息字符串(strerror)。
13.日历时间和进程时间
日历时间是是自1970年1月1日00:00:00以来国际标准时间(UTC)所经过的秒数累计值。
进程时间,这也被称为CPU时间,用以度量进程使用的中央处理机资源。进程时间以时钟滴答计算,多年来,每秒钟取为50、60或100个滴答。系统基本数据类型clock_t保存这种时间值。
使用time命令可以获得一个进程执行花费的时间。
14.时间和日期函数
#include <time.h> time_t time(time_t * calptr); 返回:若成功则为时间值,若出错则为-1
time函数返回当前日历时间,时间值作为函数值返回。如果参数非null,则时间值也存放在由calptr指向的单元内。
#include <time.h> struct tm *gmtime(const time_t *calptr); struct tm *localtime(const time_t * calptr); 两个函数返回指向tm结构的指针: struct tm { /* a broken-down time */ int tm_sec; /* seconds after the minute: [0, 61] */ int tm_min; /* minutes after the hour: [0, 59] */ int tm_hour; /* hours after midnight: [0, 23] */ int tm_mday; /* day of the month: [1, 31] */ int tm_mon; /* month of the year: [0, 11] */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday: [0, 6] */ int tm_yday; /* days since January 1: [0, 365] */ int tm_isdst; /* daylight saving time flag: <0, 0, >0 */ };
两个函数localtime和gmtime将日历时间变换成以年、月、日、时、分、秒、周日表示的时间,并将这些存放在一个tm结构中。localtime和gmtime之间的区别是:
- localtime将日历时间变换成本地时间(考虑到本地时区和夏时制标志);
- 而gmtime则将日历时间变换成国际标准时间的年、月、日、时、分、秒、周日。
#include <time.h> time_t mktime(struct tm *tmptr); 返回:若成功则为日历时间,若出错则为-1
函数mktime以本地时间的年、月、日等作为参数,将其变换成time_t值。
#include <time.h> char *asctime(const struct tm *tmptr); char *ctime(const time_t *calptr); 两个函数返回:指向null结尾的字符串
asctime和ctime函数产生形式的26字节字符串,这与date命令的系统默认输出形式类似。asctime的参数是指向年、月、日等字段结构的指针,而ctime的参数则是指向日历时间的指针。
#include <time.h> size_t strftime(char *buf, size_t maxsize, const char *format, \ const struct tm *tmptr); 返回:若有空间,则存入数组的字符数,否则为0
它是非常复杂的printf类的时间值函数。format中的变换说明:
格式 | 说明 | 示例 |
---|---|---|
%a | 缩写的周日名 | Tue |
%A | 全周日名 | Tuesday |
%b | 缩写的月名 | Jan |
%B | 月全名 | January |
%c | 日期和时间 | Tue Jan 14 19:40:30 1992 |
%d | 月日:[01, 31] | 14 |
%H | 小时(每天24小时):[00, 23] | 19 |
%I | 小时(上、下午各12小时):[01, 12] | 07 |
%j | 年日:[001, 366] | 014 |
%m | 月:[01, 12] | 01 |
%M | 分:[00, 59] | 40 |
%p | AM/PM | PM |
%S | 秒:[00, 61] | 30 |
%U | 星期日周数:[00, 53] | 02 |
%w | 周日:[0=星期日,6] | 2 |
%W | 星期一周数:[00, 53] | 02 |
%x | 日期 | 01/14/92 |
%X | 时间 | 19:40:30 |
%y | 不带公元的年:[00, 991] | 92 |
%Y | 带公元的年 | 1992 |
%Z | 时区名 | MST |
四个函数localtime, mktime, ctime和strftime受到环境变量TZ的影响。如果定义了TZ,则这些函数将使用其值以代替系统默认时区。如果TZ定义为空串(亦即TZ= ),则使用国际标准时间。TZ的值常常类似于:TZ = EST5EDT,但是POSIX.1允许更详细的说明。