Duangw

系统和时间

索引:

  1. 命令行处理函数getopt和getsubopt
  2. 动态库的动态加载
  3. 出错函数strerror和perror
  4. 系统限制
  5. 一些基本数据类型
  6. 存取口令文件信息的函数
  7. 存取组文件信息的函数
  8. 存取和设置添加组ID函数
  9. 登录会计文件
  10. 系统标识函数uname
  11. 精灵进程(daemon)
  12. syslog机制
  13. 日历时间和进程时间
  14. 时间和日期函数

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返回':',此时可以为选项参数假定一些其他默认值。

外部变量的含义:

许多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:

注意:共享库要存放在标准目录下,或在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下:

 

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有很多限制。如果不满足其中任何一个限制,则结果是未定义的:

  1. _PC_MAX_CANON、_PC_MAX_INPUT以及_PC_VDISABLE所涉及的文件必须是终端文件。
  2. _PC_LINK_MAX所涉及的文件可以是文件或目录。如果是目录,则返回值用于目录本身(不用于目录内的文件名项)。
  3. _PC_NAME_MAX和_PC_NO_TRUNC所涉及的文件必须是目录,返回值用于该目录中的文件名。
  4. _PC_PATH_MAX涉及的必须是目录。当所指定的目录是工作目录时,返回值是相对路径名的最大长度。(不幸的是,这不是我们想要知道的一个绝对路径名的实际最大长度)
  5. _PC_PIPE_BUF所涉及的文件必须是管道、FIFO或目录。在管道或FIFO情况下,返回值是对所涉及的管道或FIFO的限制值。对于目录,返回值是对在该目录中创建的任一FIFO的限制值。
  6. _PC_CHOWN_RESTRICTED必须是文件或目录。如果是目录,则返回值指明此选择项是否适用于该目录中的文件。

返回值的详细说明:

 

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系统都提供下列两个数据文件:

登录时,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)是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。

编写一个精灵进程,有如下步骤:

  1. 首先做的是调用fork,然后使父进程exit。这样做实现了下面几点:第一、如果该精灵进程是由一条简单shell命令起动的,那么使父进程终止使得shell认为这条命令已经执行完成。第二、子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的setsid调用是必要的前提条件。
  2. 调用setsid以创建一个新对话期。使调用进程:(a)成为新对话期的首进程,(b)成为一个新进程组的首进程,(c)没有控制终端。在SVR之下,有些人建议在此时再调用fork,并使父进程终止。第二个子进程作为精灵进程继续运行。这样就保证了该精灵进程不是对话期首进程,于是按照SVR4规则,可以防止它取得控制终端。另一方面,为了避免取得控制终端,无论何时打开一个终断设备都要指定O_NOCTTY。
  3. 将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。
  4. 将文件方式创建屏蔽字设置为0。
  5. 关闭不再需要的文件描述符。

 

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:

facility可以选取如下的值。设置facility参数的目的是让配置文件可以说明,来自不同设施的消息以不同的方式进行处理。如果不调用openlog,或者以facility为0来调用它,那么在调用syslog时,可将facility作为priority参数的一个部分进行说明:

调用syslog函数产生一个记录消息。其priority参数是facility和level的组合,level值按优先级从最高到最低按序排列如下:

  1. LOG_EMERG:紧急(系统不可使用)(最高优先级)
  2. LOG_ALERT:必须立即修复的条件
  3. LOG_CRIT:临界条件(例如,硬设备出错)
  4. LOG_ERR:出错条件
  5. LOG_WARNING:警告条件
  6. LOG_NOTICE:正常,但重要的条件
  7. LOG_INFO:信息性消息
  8. 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之间的区别是:

#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允许更详细的说明。