UNIX环境高级编程(2):概述(2)

来源:互联网 发布:mac 下 ftp上传工具 编辑:程序博客网 时间:2024/06/06 06:17

出错处理:

当UNIX函数出错时,常常返回一个负值,而且整型变量errno通常被设置为包含附加信息的一个值。头文件<errno.h>中定义了符号errno以及可以赋予它的各种常量,这些常量都以字符E开头。而且UNIX系统手册第2部分的intro(2)列出了所有这些出错常量(在Linux中,这些出错常量在errno(3)手册页中列出)

在支持线程的环境中,多个线程共享进程地址空间,每个线程都有属于它自己的局部errno以避免一个线程干扰另一个线程。

关于errno应该知道两条规则:(1)如果没有出错,则其值不会被一个例程清除。因此,仅当函数的返回值指明出错时,才检验其值。(2)任一函数都不会将errno设置为0。

C标准定义了两个函数,用来帮助打印出错消息。

#include <string.h>

char *strerror(int errnum)

该函数将errnum(通常就是errno)映射为一个出错信息字符串,并返回指向此字符串的指针。

#include <stdio.h>

void perror(const char *msg)

该函数基于errno的当前值,在标准出错上产生一条出错消息,然后返回。它首先输出msg指向的字符串,然后是一个冒号,一个空格,接着才是errno值对应的出错信息,最后是一个换行符。

以下程序展示了这两个函数的用法:

/* * Copyright (C) fuchencong@163.com */#include <string.h>#include <stdio.h>#include <stdlib.h>#include <errno.h>intmain(int argc, char *argv[]){fprintf(stderr, "EACCES : %s\n", strerror(EACCES));errno = ENOENT;perror(argv[0]);exit(0);}

程序中,将程序名(argv[0])作为参数传递给perror,这是一个标准的UNIX惯例。这样在程序作为管道线的一部分执行时,就能分清错误信息是由哪个程序产生的。

出错恢复:可将<errno.h>中定义的各种出错分为致命性的和非致命性的两类。对于致命性的错误,无法执行恢复动作,最多只能在用户屏幕上打印一条出错消息,或者将出错消息写入日志文件中,然后终止。但是对于非致命性的出错,有时可以较妥善地处理。这种非致命性的出错在本质上都是暂时的,例如资源短缺。对于与资源相关的非致命性出错,一般动作是延迟一些时间,然后再试。最后,取决于应用程序的开发者,他可以决定哪些错误是可恢复的。如果使用一种从错误中恢复的合理策略,由于避免了应用程序的异常终止,就能改善应用程序的健壮性。

用户标识:

口令文件(/etc/passwd)登录项中的用户ID是个数值,它向系统标识各个不同的用户。内核使用用户ID检验该用户是否具有执行某些操作的权限。

用户ID为0的用户为根用户(root)或超级用户,我们称root用户的特权为超级用户特权。如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。而且某些系统功能只限于向超级用户提供,超级用户对系统有自由支配权。

组ID:口令文件登录项中也包括用户的组ID,它是一个数值。组被用于将若干用户分到不同的项目组或部门中。这种机制允许同组的各个成员之间共享资源。组文件将组名映射为组ID,通常组文件为/etc/group.

下列程序打印用户ID和组ID:

/* * Copyright (C) fuchencong@163.com */#include <stdio.h>#include <unistd.h>#include <stdlib.h>intmain(void){printf("uid = %d, gid = %d\n", getuid(), getgid());exit(0);}

附加组ID:除了在口令文件中对一个登录名指定一个组ID外,大多上UNIX系统版本还允许一个用户属于另外的组。POSIX要求系统至少要支持8个附加组,实际上大多数系统至少支持16个附加组。

信号:

信号是通知进程已经发生某种情况的一种技术。进程如何处理信号有三种选择:

(1)忽略该信号。

(2)按系统默认方式处理。

(3)提供一个函数,信号发生时则调用该函数,这被称为捕捉该信号。

很多情况下会产生信号。在终端键盘上有两种产生信号的方法:中断键(CTRL+C)和退出键(CTRL+\),它们被用来中断当前运行的进程。另一种产生信号的方法是调用名为kill的函数。在一个进程中调用这个函数就可以向另一个进程发送一个信号。但是这样做也有一些限制:当向一个进程发送信号时,我们必须是该进程的所有者或超级用户。

上篇文章编写了一个简单的shell程序,当运行该程序时,按下CTRL+C键,则此进程终止。因为按下中断键会产生SIGINT信号,而我们的程序没有告诉系统内核对该信号如何处理,因此系统按默认方式处理:终止该进程。

接下来的程序对SIGINT信号进行了捕捉,该程序通过调用signal函数来指定当产生SIGINT信号时要调用的函数名。

/* * Copyright (C) fuchencong@163.com */#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <unistd.h>#include <string.h>#define MAX_LINE 128static void sig_int(int);intmain(void){char buf[MAX_LINE];pid_t pid;int status;if (signal(SIGINT, sig_int) == SIG_ERR) {printf("signal error\n");exit(1);}printf("%% ");while (fgets(buf, MAX_LINE, stdin) != NULL) {if (buf[strlen(buf) - 1] == '\n') {buf[strlen(buf) - 1] = '\0';}if ( (pid = fork()) < 0) {printf("fork error\n");exit(2);} else if (pid == 0) {/* child process */execlp(buf, buf, (char *)0);printf("can't execute %s\n", buf);exit (3);}if (waitpid(pid, &status, 0) < 0) {printf("waitpid error\n");exit (4);}printf("%% ");}exit(0);}voidsig_int(int signo){printf("interrupt\n%% ");}

因为大多数重要的应用程序都将使用信号,后续文章还将进一步学习信号。

时间值:

长期以来,UNIX系统一直使用两种不同的时间值:

(1)日历时间,该值是自1970.1.1 00:00:00以来的国际标准时间(UTC)所经过的秒数累计值。系统的基本数据类型time_t用于保存这种时间值。

(2)进程时间:也被称为CPU时间,用以度量进程使用的中央处理机资源。进程时间以时钟滴答计算。历史上曾取每秒钟为50,60或100个滴答。系统基本数据类型clock_t用于保存这种时间。

用以度量一个进程的执行时间时,UNIX系统使用三个进程时间值:时钟时间,用户CPU时间,系统CPU时间。时钟时间又称为墙上时钟时间,它是进程运行的时间总量,其值与系统同时运行的进程数有关。用户CPU时间是执行用户指令所用的时间。系统CPU时间是为该进程执行内核程序所经历的时间。例如每当一个进程执行一个系统服务时(例如read或write调用),则在内核内执行该服务所花费的时间就计入该进程的系统CPU时间。用户CPU时间和系统CPU时间之和常常被称为CPU时间。

在UNIX中,要获取这三个时间值是很容易的,只要执行命令time,其参数是要度量其执行时间的命令。

系统调用和库函数:

所有操作系统都提供多种服务的入口点,程序由此向内核请求服务。各种版本的UNIX实现都提供定义明确,数量有限,可直接进入内核的入口点,这些入口点被称为系统调用。系统调用接口总是在《UNIX程序员手册》的第2部分中说明。

UNIX所使用的技术是为每个系统调用在标准C库中设置一个具有同样名字的函数。用户进程用标准C调用序列来调用这些函数。而这些函数又用系统所要求的技术调用相应的内核服务。

UNIX程序员手册的的第三部分定义了程序员可以使用的通用函数。虽然这些函数可能会调用一个或多个系统调用,但是它们并不是内核的入口点。例如printf函数会调用write系统调用以输出一个字符串,而atoi则并不要使用任何系统调用。

尽管系统调用和库函数都可以为应用程序提供服务,但是应当理解,必要时我们可以替换库函数,而通常却不能替换系统调用。应用程序可以调用系统调用或者库函数,而很多库函数则会调用系统调用。

系统调用和库函数之间的另一个区别是:系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。


0 0
原创粉丝点击