UNIX環境高級編程(2):概述(2)

出錯處理:

當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) [email protected]
 */


#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int
main(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) [email protected]
 */


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


int
main(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) [email protected]
 */


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

#define MAX_LINE 128

static void sig_int(int);

int
main(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);
}


void
sig_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則並不要使用任何系統調用。

儘管系統調用和庫函數都可以爲應用程序提供服務,但是應當理解,必要時我們可以替換庫函數,而通常卻不能替換系統調用。應用程序可以調用系統調用或者庫函數,而很多庫函數則會調用系統調用。

系統調用和庫函數之間的另一個區別是:系統調用通常提供一種最小接口,而庫函數通常提供比較複雜的功能。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章