UNIX基礎知識:UNIX體系結構、登錄、文件和目錄、輸入和輸出、程序和進程、出錯處理、用戶標識、信號、時間值、系統調用和庫函數

引言:

? ? ? ? 所有的操作系統都為運行在其上的程序提供服務,比如:執行新程序、打開文件、讀寫文件、分配存儲區、獲得系統當前時間等等

1. UNIX體系結構

? ? ? ? 從嚴格意義上來說,操作系統可被定義為一種軟件,它控制計算機硬件資源,提供程序運行的環境。我們通常將這種軟件稱為內核(kernel),因為它相對較小,而且位于環境的核心。圖1-1 顯示了 UNIX 操作系統的體系結構。

? ? ? ? ?內核的接口被稱為系統調用(system call),公用函數庫構建在系統調用接口之上,應用程序可以使用公用函數庫提供的接口,也可以使用內核提供的接口(系統調用)。shell 是一個特殊的應用程序,為運行其他應用程序提供了一個接口。從廣義上說,操作系統包括內核和一些其他軟件,這些軟件使得計算機能夠發揮作用,并使計算機具有自己的特性。這里所說的其他軟件包括系統實用程序(system utility)、shell、公用函數庫以及應用程序等。Linux 是 GUN 操作系統使用的內核,一些人將這種操作系統稱為 GUN/Linux 操作系統,但是更常見的是簡單地稱其為 Linux。

2. 登錄

2.1 用戶名

? ? ? ? 用戶在登錄 UNIX 系統時,輸入用戶名,然后輸入用戶密碼。系統將在其口令文件(通常是 /etc/passwd 文件)中查看登錄名。口令文件中的登錄項由 7 個以冒號(:) 分隔的字段組成,依次是【登錄名:加密口令:用戶id:用戶組id:注釋字段:用戶登錄進入的起始工作目錄:shell 程序路徑】

在 shell 終端下輸入以下命令查看 /etc/passwd 文件的內容進行驗證:

cat /etc/passwd

用戶名:root
加密口令:x
用戶id:0
用戶組id:0
注釋字段:root
用戶登錄進入的起始工作目錄:/root
用戶的 shell 程序路徑:/bin/bash

? ? ? ? 加密口令:x 是一個占位符,較早期的 UNIX 系統版本中,該字段存放加密口令字。將加密口令字存放在一個人人可讀的文件中是一個安全漏洞,所以現在將加密口令字存放在另一個文件中。第6章將說明這種文件以及訪問它們的函數。

?2.2 shell

? ? ? ? 用戶登錄后,系統通常先顯示一些系統信息,然后用戶就可以向 shell 程序輸入命令。shell 是一個命令行解釋器,它讀取用戶輸入,然后執行命令。shell 的用戶輸入通常來自終端(交互式 shell),有時則來自于文件(稱為 shell 腳本)。

3. 文件和目錄

3.1 文件系統

? ? ? ? UNIX 文件系統是目錄和文件的一種層次結構,所有東西的起點是根目錄(root),這個目錄的名稱是一個字符 “/”。

? ? ? ? 目錄是一個包含目錄項的文件。在邏輯上,可以認為每個目錄項都包含一個文件名,同時還包含說明該文件屬性的信息。文件屬性是指文件類型(普通文件還是目錄等)、文件大小,文件所有者、文件權限(其他用戶是否有訪問該文件的權限)以及文件的最后修改時間等。

3.2 文件名

? ? ? ? 文件的名字稱為文件名(filename),只有斜線(/)空字符這兩個字符不能出現在文件名中。斜線用來分隔構成路徑名的,空字符則用來終止一個路徑名。

? ? ? ? 創建新目錄時會自動創建兩個文件名:. (點)和 ..(點點) 。點指向當前目錄,點點指向父目錄。最高層次的根目錄中,點和點點都指向當前目錄。

3.3 路徑名

? ? ? ? 由斜線分隔的一個或多個文件名的序列(也可以斜線開頭)構成路徑名(pathname)。以斜線開頭的路徑名稱為絕對路徑(absolute pathname),否則稱為相對路徑(relative pathname),相對路徑名指向相對于當前目錄的文件。文件系統根的名字(/)是一個特殊絕對路徑名,它不包含文件名。

以下代碼功能:列出一個目錄中所有文件的名字,ls 命令的簡要實現

err.h

#ifndef ERR_H_
#define ERR_H_#include <stdarg.h>#define MAX_BUF 4096// __attribute__((noreturn)) 屬性,告訴編譯器這個函數永遠不會有返回值,避免當函數有返回值,在某種條件下未能執行到返回值代碼處,編譯器警告或報錯
__attribute__((noreturn)) void err_quit(const char *format, ...);
__attribute__((noreturn)) void err_sys(const char *format, ...);
void err_ret(const char *format, ...);#endif /*ERR_H_*/

err.c

#include "err.h"#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>static void err_doit(const int errno_flag, const int errorno, const char *format, va_list ap);static void err_doit(const int errno_flag, const int errorno, const char *format, va_list ap)
{char buf[MAX_BUF] = "";vsnprintf(buf, MAX_BUF-1, format, ap); // 將可變參數列表格式化到字符串中if (errno_flag)snprintf(buf+strlen(buf), MAX_BUF-strlen(buf)-1, ": %s", strerror(errorno));strcat(buf, "\n"); // 將參數字符串 "\n" 拷貝到參數 buf 所指的字符串尾。buf 要有足夠的空間來容納要拷貝的字符串。fflush(stdout); // 刷新 stdoutfputs(buf, stderr); // 將 buf 所指的字符串寫入到 strerrfflush(NULL); // 參數為 NULL, fflush() 會刷新所有 stdio
}void err_quit(const char *format, ...)
{va_list ap;va_start(ap, format); // 獲取可變參數列表的第一個參數的地址err_doit(0, 0, format, ap);va_end(ap); // 清空va_list可變參數列表exit(1);
}void err_sys(const char *format, ...)
{va_list ap;va_start(ap, format);err_doit(1, errno, format, ap);va_end(ap);exit(1);
}void err_ret(const char *format, ...)
{va_list ap;va_start(ap, format);err_doit(1, errno, format, ap);va_end(ap);
}

list_dir_filename.c

#include "../common/err.h"#include <dirent.h>
#include <stdio.h>int main(int argc, char *argv[])
{DIR *dirp = NULL;struct dirent *direntp = NULL;if (argc != 2)err_quit("ls dir_name");/* 打開目錄 */if ((dirp = opendir(argv[1])) == NULL)err_sys("can't open %s", argv[1]);/* 逐一讀取目錄條目 */while ((direntp = readdir(dirp)) != NULL)printf("%s\n", direntp->d_name);/* 關閉目錄 */closedir(dirp);return 0;
}

3.4 工作目錄

? ? ? ? 每個進程都有一個工作目錄(working directory),有時稱為當前工作目錄(current working directory)。所有相對路徑名都從工作目錄開始解釋的。進程可以用 chdir() 函數更改其工作目錄。

? ? ? ? 例如,相對路徑名 unix_env_program/chapter_one/test 指的是當前工作目錄中的 unix_env_program 目錄中的 chapter_one 目錄中的 test 文件(或目錄)。從路徑名可以看出?unix_env_program 和 chapter_one 是目錄,但是不能分辨 test 是文件還是目錄。/etc/passwd 是一個絕對路徑名,它指的是根目錄下的 etc 目錄中的 passwd 文件(或目錄)

3.5 起始目錄

? ? ? ? 登錄時,工作目錄設置為起始目錄(home directory),起始目錄從口令文件(/etc/passwd)中相應用戶的登錄項中獲得。

4. 輸入和輸出

4.1 文件描述符

? ? ? ? 文件描述符(file descriptor)通常是一個小的非負整數,內核用以標識一個特定進程正在訪問的文件。當內核打開一個現有的文件或創建一個新文件時,它都會返回一個文件描述符。在讀寫這個文件時,可以使用這個文件描述符。

4.2 標準輸入、標準輸出和標準錯誤

? ? ? ? 每當運行一個新程序時,所有的 shell 都為其打開 3 個文件描述符,即標準輸入(standard input)、標準輸出(standard output)和標準錯誤(standard error),STDIN_FILENO(0)、?STDOUT_FILENO(1)和 STDERR_FILENO(2)為 3 個常量,定義在 #include <unistd.h> 頭文件中,它們指向了標準輸入、標準輸出和標準出錯的文件描述符。如果不做特殊處理,例如就像簡單的命令 ls,則這 3 個文件描述符都鏈接向終端。大多數 shell 都提供一種方法,使其中任何一個或所有這 3 個文件描述符都能重新定向到某個文件,例如:

ls > file.list // 執行 ls 命令,將其標準輸出重新定向到名為 file.list 的文件中。

4.3 不帶緩沖的 I/O

? ? ? ? ?函數 open()、read()、write()、lseek() 以及 close() 提供了不帶緩沖的 I/O,這些函數操作的都是文件描述符

以下代碼功能:從標準輸入中讀,并向標準輸出中寫,用于復制任一個 UNIX 普通文件

stdin_r_stdout_w.c

#include "../common/err.h"#include <unistd.h>#define BUF_SIZE 4096int main(int argc, char *argv[])
{int nread = 0;char buf[BUF_SIZE] = "";while ((nread = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) // 從標準輸入文件描述符中讀{if (write(STDOUT_FILENO, buf, nread) != nread) // 寫入標準輸出文件描述符中err_sys("write error");}if (nread < 0)err_sys("read error");return 0;
}

將程序編譯成 stdin_r_stdout_w 可執行文件,在 shell 終端輸入以下命令

$./stdin_r_stdout_w > data.stdout // 標準輸入是終端,標準輸出重定向到 data.stdout 文件,標準出錯也是終端

鍵盤輸入:111,再換行,再輸入:222,再換行,再按下文件結束符(ctrl +d),將終止本次復制。

在 shell 終端輸入以下命令

$ ./stdin_r_stdout_w < data.stdout > newdata.stdout
// 標準輸入重定向為 data.stdout 文件,標準輸出重定向為 newdata.stdout 文件,標準出錯是終端

此時,是將 data.stdout 文件復制一份,并命名為 newdata.stdout

?4.4 標準 I/O

? ? ? ? 標準 I/O 函數為那些不帶緩沖的 I/O 函數提供了一個帶緩沖的接口。使用標準 I/O 函數無需擔心如何選取最佳的緩沖區大小。使用標準的 I/O 函數還簡化了對輸入行的處理。例如,fgets() 函數讀取一個完整的行,而 read() 函數讀取指定的字節數。我們最熟悉的標準 I/O 函數是 printf(),在調用 printf() 函數的程序中,總是要 #include <stdio.h> ,該頭文件包括了所有標準 I/O 函數的原型。

? ? ? ? 標準 I/O 操作的對象是文件指針:FILE*

? ? ? ? 特殊的文件指針:stdin、stdout、stderr

以下代碼功能:從標準輸入復制到標準輸出,也是用于復制任一個 UNIX 普通文件

stdin_copy_to_stdout.c

#include "../common/err.h"#include <stdio.h>int main(int argc, char *argv[])
{int c = 0;while ((c = getc(stdin)) != EOF) // 一次讀取一個字符,直到讀入最后的一個字節時,返回 EOF{if (putc(c, stdout) == EOF) // 將字符寫入到標準輸出err_sys("output error");}if (ferror(stdin))err_sys("input error");return 0;
}

常量 EOF ,標準 I/O 常量 標準輸入(stdin)和 標準輸出(stdout)都在 #include <stdio.h> 頭文件中定義。

5. 程序和進程

5.1 程序

? ? ? ? 程序(program)是一個存儲在磁盤上某個目錄中的可執行文件。內核使用 exec() 函數,將程序讀入內存,并執行程序。

5.2 進程和進程ID

? ? ? ? 程序的執行實例被稱為進程(progress)。UNIX 系統確保每個進程都有一個唯一的數字標識符,稱為進程ID(progress ID)。進程ID 總是為一個非負整數。

以下代碼功能:打印進程ID

print_pid.c

#include <unistd.h>
#include <stdio.h>int main(int argc, char *argv[])
{printf("pid = %ld\n", getpid());return 0;
}

5.3 進程控制

? ? ? ? fork()、exec() 和 waitpid() 是 3 個主要用于進程控制的主要函數。

? ? ? ? 以下代碼功能:從標準輸入中讀取命令,然后執行這些命令,類似于 shell 程序的基本實施部分。

execlp_demo.c

#include "../common/err.h"#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char buf[MAX_BUF] = "";pid_t pid = 0;int status = 0;printf("%%");while ((fgets(buf, MAX_BUF, stdin)) != NULL) // 從標準輸入 stdin 中讀取一行{if (buf[strlen(buf) - 1] == '\n') // 最后一個是換行符時buf[strlen(buf) - 1] = 0; // 替換成 NULL,因為 execlp() 函數的參數要求字符串必須要以字符串結束符('\0')結尾if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) { // 子進程/*int execlp(const char *file, const char *arg, ...);(1)execlp 函數名中 l 是 list,說明這個函數的參數是可變參數列表的意思(2)execlp 函數名中 p 是環境變量 $PATH 的意思,說明,這個函數的第一個參數可以是一個文件名,這時函數會從環境變量中去搜索這個文件的完整路徑名;這個函數的第一個參數也可以是絕對路徑或相對路徑,這時函數不會從環境變量中去搜索這個文件的完整路徑名(3)第二個參數 arg 是命令行起始地址(4) 傳遞給這個函數的最后一個參數必須為*/ execlp(buf, buf, (char *)NULL); // 執行讀取的命令err_ret("can't exec: %s", buf); // 如果程序能跑到這里,說明執行 execlp() 函數出錯了exit(127); // 子進程退出}/*pid_t waitpid(pid_t pid, int *status, int options);默認情況下 (當 options=0 時 ),waitpid掛起調用進程的執行,直到它的等待集合 (wait set) 中的一個子進程終止。如果等待集合中的一個進程在剛調用的時刻就已經 終止了,那么 waitpid 就立即返回 。在這兩種情況中,waitpid返回導致 waitpid 返回的已終止子進程的PID此時,已終止的子進程已經被回收,內核會從系統中刪除掉它的所有痕跡。*/// 父進程if ((pid = waitpid(pid, &status, 0)) < 0) // 子進程退出時,父進程回收子進程的資源,options=0,父進程被掛起,直到子進程退出,waitpid 立即返回err_sys("waitpid error");printf("%%");}return 0;
}

5.4 線程和線程ID?

????????通常,一個進程只有一個控制線程(thread)--?某一個時刻執行的一組機器指令。對于某些問題,如果有多個控制線程分別作用于它的不同部分,那么解決起來就容易的多。另外,多個控制線程也可以充分利用多核處理器的并行執行任務的能力。

? ? ? ? 一個進程內的所有線程共享同一個地址空間、文件描述符、棧以及與進程相關的屬性。因為它們能訪問同一個存儲區,所以各線程在訪問共享數據時需要采取同步措施以避免數據不一致的問題。

? ? ? ? 和進程相同,線程也有 ID 標識。但是,線程ID 只在它所屬的進程內起作用。一個進程的線程ID 在另一個進程中沒有意義。當在一個進程中對某個特定線程進行處理時,我們可以使用該線程的 ID 引用它。

6. 出錯處理

6.1 errno 以及出錯打印函數 strerror() 和 perror()

? ? ? ? 當 UNIX 系統函數出錯時,通常會返回一個負值,而且整型變量 errno 通常被設置為具有特定出錯信息的值。例如,open() 函數如果成功執行則返回一個非負文件描述符,如果出錯則返回 -1,errno 將會被設置,通過調用 char *str?= strerror(errno); 獲取到出錯信息。而有些函數出錯則使用另一種約定而不是返回一個負值。例如,大多數返回指向對象指針的函數,在出錯時會返回一個 NULL 指針。

? ? ? ? 頭文件 <errno.h> 中定義了 errno 以及賦予它的各種常量。這些常量都以字符 E 開頭。在 linux 操作系統中,出錯常量在 man 3 errno 手冊頁查看

? ? ? ? POSIX 和 ISO C 將 errno 定義為一個符號,它擴展成為一個可修改的整型左值(lvalue),它可以是一個包含出錯編號的整數,也可以是一個返回出錯編號指針的函數。以前使用的定義是:extern int errno;

但在支持線程的環境中,多個線程共享進程地址空間,每個線程都有屬于它自己的局部 errno,以避免一個線程干擾另一個線程。linux 操作系統支持多線程存取 errno,將其定義為:

extern int* __errno_location(void);

#define errno *(__errno_location())

對于 errno 應當注意兩條規則。

(1)如果沒有出錯,其值不會被例程清除,因此,僅當函數的返回值指明出錯時,才檢驗其值。

(2)任何函數都不會將 errno 的值設置為 0,而且在 <errno.h> 中定義的所有常量都不為 0。

C 標準定義了兩個函數,用于打印出錯信息

#include <string.h>
char* strerror(int errnum); // 返回值指向出錯信息字符串的指針

strerror() 函數將 errnum(通常就是一個 errno 值)映射為一個出錯信息字符串,并且返回此出錯信息字符串的指針。

#include <stdio.h>
void perror(const char *msg);

perror 函數基于 errno 的當前值,在標準錯誤上參生一條出錯信息,然后返回。它輸出的格式為:

參數 msg 指向的字符串,然后是一個冒號,一個空格,接著是 errno 值對應的出錯信息字符串,最后是一個換行符。

以下代碼功能:展示 strerror() 和 perror() 函數的使用方法

strerror_perror.c

#include <stdio.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[])
{fprintf(stderr, "EACCES: %s\n", strerror(EACCES));errno = ENOENT;perror(argv[0]);return 0;
}

?注意:我們將程序名(argv[0])作為參數傳遞給 perror()?函數,這是一個標準的 UNIX 慣例,使用這種方法,在程序作為管道的一部分執行時,例如:

prog1 < inputfile | prog2 | prog3 > outputfile

我們就能夠分清 3 個程序中哪一個產生了一條特定的出錯信息。

6.2?出錯恢復

? ? ? ? 可將在? <errno.h> 中定義的各種出錯分成兩類:致命性的和非致命性的。

? ? ? ? 對于致命性的錯誤,無法執行恢復動作。最多能做的是在用戶屏幕上打印一條出錯消息或將一條出錯消息寫入日志文件中,然后退出。

? ? ? ? 對于非致命性出錯,有時可以較為妥善地進行處理。大多數非致命性出錯是短暫的(如資源短缺),當系統中活動較少時,這種出錯很可能不會發生。

? ? ? ? 與資源相關的非致命性出錯包括:

????????EAGAIN 資源暫時不可用(可能與 EWOULDBLOCK?的值相同)

????????ENFILE 系統中打開的文件太多

????????ENOBUFS 沒有可用的緩沖空間

????????ENOLCK 沒有可用的鎖

????????ENOSPC 設備上沒有剩余空間

????????EWOULDBLOCK 操作將阻塞(可能與 EAGAIN 的值相同)

????????ENOMEM 沒有足夠的空間。有時也是非致命性出錯。

????????EBUSY 設備或資源忙。當指明共享資源正在使用時,也可將它作為非致命性的出錯處理。

????????EINTR 中斷函數調用。當中斷一個慢速系統調用時,也可將它作為非致命性出錯處理。

? ? ? ? 對于資源相關的非致命性出錯的典型恢復操作是延遲一段時間,然后重試。這種技術可應用于其他情況,例如,假設出錯表明一個網絡連接不再起作用,那么應用程序可以采取這種方法,在短時間延遲后,嘗試重新連接。一些應用使用指數補償算法,在每次迭代中等待更長時間。

? ? ? ? 最終,由應用的開發者決定在哪些情況下應用程序可以從出錯中恢復。如果能夠采用一種合理的恢復策略,那么可以避免應用程序異常終止,進而就能改善應用程序的健壯性。

7. 用戶標識

7.1 用戶ID

? ? ? ? 口令文件登錄項中的用戶ID(user ID)是一個數值,系統用它來標識各個不同的用戶。系統管理員在確定一個用戶的登錄名的同時,確認其用戶ID。用戶不能更改其用戶ID。通常每個用戶有一個唯一的用戶ID。下面將介紹內核如何使用用戶ID 來檢驗該用戶是否有執行某些操作的權限。

? ? ? ? 用戶ID為 0 的用戶為根用戶(root)或超級用戶(superuser)。在口令文件中,有一個登錄項,其登錄名為 root,我們稱這種用戶的特權為超級用戶特權。如果一個進程具有超級用戶特權,則大多數文件權限檢查都不再進行。

7.2 用戶的組ID

? ? ? ? 口令文件登錄項也包括用戶的組ID(group ID),它是一個數值。組ID 也是由系統管理員在指定用戶登錄名時分配的。一般來說,在口令文件中有多個登錄項具有相同的組ID。組被用于將一個或多個用戶集合到項目或部門中去,這種機制允許同組的各種成員之間共享資源(如文件)。組文件將組名映射為數值的組ID。組文件通常是 /etc/group

? ? ? ? 使用數值的用戶ID 和數值的組ID 設置權限是歷史上形成的。對于磁盤上的每個文件,文件系統都存儲該文件所有者的用戶ID 和組ID。存儲這兩個值只需 4 個字節(假定每個都以雙字節的整形數存放)。如果使用完整 ASCII 登錄名和組名,則需要更多的磁盤空間。另外,在檢驗權限期間,比較字符串比比較整形數更消耗時間。

? ? ? ? 但是,對于用戶而言,使用名字比使用數值方便,所以口令文件中包含了登錄名和用戶名ID 直接的映射關系,而組文件中包含了組名跟組ID 之間的映射關系。例如,ls -l 命令使用口令文件將數值的用戶ID映射為登錄名,從而打印出文件所有者的登錄名。

以下程序功能:打印用戶ID 和組ID

print_uid_gid.c

#include <stdio.h>
#include <unistd.h>int main(int argc, char *argv[])
{printf("uid = %d, gid = %d\n", getuid(), getgid());return 0;
}

7.3 附屬組ID

? ? ? ? 除了口令文件中對一個登錄名指定一個組ID外,大多數 UNIX 系統版本還允許一個用戶屬于另外一些組,登錄時,讀文件 /etc/group,尋找列有該用戶作為其成員的前 16 個記錄項就可以得到該用戶的附屬組ID(supplementary group ID)。POSIX 要求系統至少支持 8個附屬組,實際上大多數 UNIX 系統至少支持 16 個附屬組。

8. 信號?

? ? ? ? 信號(signal)用于通知進程發生某種情況。例如,某一進程執行除法操作,其除數為 0,則將名為 SIGFPE(浮點異常)的信號發送給該進程。

? ? ? ? 進程有以下 3 種處理信號的方式。

? ? ? ? (1)忽略信號。有些信號表示硬件異常,例如,除以 0 或訪問進程地址空間以外的存儲單元等,因為這些異常產生的后果不確定,所以不推薦使用這種處理方式。

? ? ? ? (2)按系統默認方式處理。對于除數為 0,系統默認處理方式是終止該進程。

? ? ? ? (3)提供一個函數,信號發生時調用該函數,這被稱為捕捉該信號,通過提供自編的處理函數,我們就能知道什么時候產生了信號,并按期望的方式處理它。

? ? ? ? 很多情況都會產生信號。終端鍵盤上有兩種產生信號的方法:

????????(1)中斷鍵(interrupt key,通常是 delete 鍵或 ctrl+c)和退出鍵(quit key,通常是 ctrl+\),它們被用于中斷當前運行的進程。

? ? ? ? (2)另一種產生信號的方法是調用 kill 函數,在一個進程中調用 kill 函數就可以向另一個進程發送一個信號,當然這樣做也是有限制的,當向一個進程發送信號時,我們必須是那個進程的所有者或超級用戶。

以下代碼功能:測試捕捉 SIGINT 信號,即捕捉按下中斷鍵產生的信號

signal_int_demo.c

#include "../common/err.h"#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>static void sig_interrupt(int signo);int main(int argc, char *argv[])
{char buf[MAX_BUF] = "";int status = 0;pid_t pid = 0;if (signal(SIGINT, sig_interrupt) == SIG_ERR)err_sys("signal error");printf("%% ");while (fgets(buf, MAX_BUF, stdin) != NULL){if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = 0;if ((pid = fork()) < 0)err_sys("fork error");else if (pid == 0) // 子進程{execlp(buf, buf, (char*)NULL);err_ret("can't exec: %s", buf);exit(127);}// 父進程if ((pid = waitpid(pid, &status, 0)) < 0) // 父進程回收子進程資源err_sys("waitpid error");printf("%% ");}return 0;
}void sig_interrupt(int signo)
{printf("interrupt\n%% ");
}

9. 時間值

? ? ? ? 歷史上,UNIX 系統使用過兩種不同的時間值。

? ? ? ? (1)日歷時間。該值是自協調世界時(Coordinated Universal Time,UTC),自 1970年1月1日 00:00:00 這個特定時間以來所經過的秒數累計值(早期的手冊稱 UTC 為格林尼治標準時間)。這些時間值可用于記錄文件最近一次的修改時間等。系統基本數據類型 time_t 用于保存這種時間值。

? ? ? ? (2)進程時間,也被稱為 CPU時間,用以度量進程使用的中央處理器資源。進程時間以時鐘滴答計算,每秒鐘曾經取為50、60或100個時鐘滴答。系統基本數據類型 clock_t 保存這種時間值,用 sysconf 函數可以得到每秒的時鐘滴答數。

? ? ? ? 當度量一個進程的執行時間時,UNIX 系統為一個進程維護了3個進程時間值:

  • 時鐘時間
  • 用戶CPU時間
  • 系統CPU時間

? ? ? ? 時鐘時間又稱為墻上時鐘時間(wall clock time),它是進程運行的時間總量,其值與系統中同時運行的進程數有關。

? ? ? ? 用戶CPU時間是執行用戶指令所用的時間量。系統CPU時間是為該進程執行內核程序所經歷的時間。例如,每當一個進程執行一個系統服務時,如 read()?或 write(),在內核內執行該服務所花費的時間就計入該進程的系統CPU時間。用戶CPU時間和系統CPU時間之和常被稱為 CPU時間。

? ? ? ? 要取得任一進程的時鐘時間、用戶CPU時間和系統CPU時間是很容易的,只要執行命令 time,其參數是要度量其執行時間的命令,例如:

$ cd /usr/include
$ time -p grep _POSIX_SOURCE */*.h > /dev/null

?10. 系統調用和庫函數

????????

11. 習題

(1)在系統上驗證,除根目錄外,目錄. 和目錄.. 是不同的

ls 命令的下面有兩個參數:

-i 打印文件或目錄的 i 節點編號。

-d 僅打印目錄信息,而不是打印目錄中所有文件的信息。

(2)分析以下打印進程ID的程序,說明進程ID為 852 和 853 的進程發生了什么?

?因為 UNIX 系統是多任務操作系統,在第一次執行程序打印了該程序的進程ID 后,系統中有其它兩個新的進程被執行,所以占用了進程ID 為 852 和 853 的進程ID 了,我們再執行程序打印進程ID 時,此時的進程ID 則為 854

(3)perror() 函數的參數是用 const 修飾定義的,而 strerror() 函數的整型參數沒有用 const 修飾定義,為什么?

????????因為 perror() 函數的 msg 參數是一個指針,perror() 函數內部就可以改變這個指針指向的字符串,用 const 修飾后,說明這個指針指向的內容是常量,perror() 函數內部就不能修改這個指針指向的字符串。

? ? ? ? 而 strerror() 函數的 errnum 參數是一個整數類型,是按值傳遞,因此即使 strerror() 函數內部對這個參數進行修改,對于函數外是無效的,也就沒必要使用 const 屬性。

(4)若日歷時間存放在帶符號的 32 位整型數中,那么到哪一年它會溢出?可以用什么方法擴展溢出浮點數?采用的策略是否與現有的應用相兼容?

? ? ? ? 2^31 / 365*24*60*60 約等于 68,而日歷時間是從1970年1月1日 00:00:00 開始的,所以 2038年會溢出。將 time_t 定義為 64 位整型數就可以了。如果它現在是 32 位整型數,修改為 64 位整型數后,要保證應用程序正常工作,應當對其重新編譯。但是還有一個更糟糕的地方,某些文件系統及備份介質是 32 位整型數存放時間的,對于這些同樣需要更新,但又需要能兼容舊的格式。

(5)若進程時間存放在帶符號的 32 位整形數中,而且每秒為 100 時間滴答,那么經過多少天后,該時間會溢出?

? ? ? ? 2^31 / 24*60*60*100 約等于 248 天

注:本文大部分內容摘抄自《UNIX環境高級編程》(第3版),少部分內容是自己的操作驗證以及寫的代碼實例,本文只作為學習筆記

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/37133.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/37133.shtml
英文地址,請注明出處:http://en.pswp.cn/news/37133.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CTFshow 限時活動 紅包挑戰7、紅包挑戰8

CTFshow紅包挑戰7 寫不出來一點&#xff0c;還是等了官方wp之后才復現。 直接給了源碼 <?php highlight_file(__FILE__); error_reporting(2);extract($_GET); ini_set($name,$value);system("ls ".filter($_GET[1])."" );function filter($cmd){$cmd…

【圖像分類】理論篇(2)經典卷積神經網絡 Lenet~Densenet

1、卷積運算 在二維卷積運算中&#xff0c;卷積窗口從輸入張量的左上角開始&#xff0c;從左到右、從上到下滑動。 當卷積窗口滑動到新一個位置時&#xff0c;包含在該窗口中的部分張量與卷積核張量進行按元素相乘&#xff0c;得到的張量再求和得到一個單一的標量值&#xff0c…

Java 集合擴容概括

參考博文&#xff1a; java集合的擴容機制_這個名字先用著的博客-CSDN博客 # ArrayList 可隨著元素的增長而自動擴容&#xff0c;正常擴容的話&#xff0c;每次擴容到原來的 1.5倍。 # ArrayList 和Vector擴容機制總結&#xff1a; ArrayList 和Vector,底層都是Object數組…

SQL- 每日一題【1327. 列出指定時間段內所有的下單產品】

題目 表: Products 表: Orders 寫一個解決方案&#xff0c;要求獲取在 2020 年 2 月份下單的數量不少于 100 的產品的名字和數目。 返回結果表單的 順序無要求 。 查詢結果的格式如下。 示例 1: 解題思路 1.題目要求我們獲取在 2020 年 2 月份下單的數量不少于 100 的產品的…

如何重置樹莓派 Pico(重置外圍設備失敗)

有時候需要重置樹莓派 Pico&#xff0c;一種方法是按住 Pico 上的“BOOTSEL”按鈕再插入 USB&#xff1b;或者用按鈕連接“RUN”和“GND”針腳&#xff0c;然后同時按下這個按鈕和“BOOTSEL”按鈕。這樣就可以進入 USB 模式&#xff0c;這樣從一定程度進行了重置。 但是這種方…

IO多路復用

常見的網絡IO模型 網絡 IO 模型分為四種&#xff1a;同步阻塞 IO(Blocking IO, BIO)、同步非阻塞IO(NIO, NewIO)、IO 多路復用、異步非阻塞 IO(Async IO, AIO)&#xff0c;其中AIO為異步IO&#xff0c;其他都是同步IO 同步阻塞IO 同步阻塞IO&#xff1a;在線程處理過程中&am…

劍指Offer10-I.斐波那契數列 C++

1、題目描述 寫一個函數&#xff0c;輸入 n &#xff0c;求斐波那契&#xff08;Fibonacci&#xff09;數列的第 n 項&#xff08;即 F(N)&#xff09;。斐波那契數列的定義如下&#xff1a; F(0) 0, F(1) 1 F(N) F(N - 1) F(N - 2), 其中 N > 1. 斐波那契數列由 0 和 …

Redis_事務操作

13. redis事務操作 13.1事務簡介 原子性(Atomicity) 一致性(Consistency) 隔離性(isolation) 持久性(durabiliby) ACID 13.2 Redis事務 提供了multi、exec命令來完成 第一步&#xff0c;客戶端使用multi命令顯式地開啟事務第二步&#xff0c;客戶端把事務中要執行的指令發…

前沿分享-通過經皮神經刺激來治療糖尿病神經性疼痛

經皮神經電刺激&#xff08;PENS&#xff09;設備用于對糖尿病周圍神經病變引起的慢性、頑固性疼痛進行多次治療。 放在耳朵上的這種可穿戴設備在幾天內持續提供低水平的脈沖電流。 這是一種安全有效的非麻醉性替代治療慢性疼痛的方法。還有一張設備放在糖足上的照片&#xff0…

向量數據庫 Milvus Cloud Partition Key:租戶數量多,單個租戶數據少的三種解決方案

三種解決方案 這個問題提出的時候,Milvus 的最新版本是 2.2.8,我們做個角色互換,在當時站在這個用戶的角度,留在我們面前的選擇有這么幾個: 為每個租戶創建一個 collection 為每個租戶創建一個 partition 創建一個租戶名稱的標量字段 接下來,我們依次分析下這三種方案的可…

《零基礎實踐深度學習》(第2版)學習筆記,(五)深度學習與計算機視覺

文章目錄 1. 計算機視覺概述2. 圖像分類3. 目標檢測 1. 計算機視覺概述 圖像分類 目標檢測 2. 圖像分類 3. 目標檢測

01-C++數據類型

3、基礎類型 3.1、簡單變量 變量的命名 carDrip和cardRip 或boat_sport和boats_port 此外&#xff0c;還有有前綴的命名&#xff0c;使用前綴表示數據類型。常見的前綴有:str&#xff08;表示字符串&#xff09;、n&#xff08;表示整數值&#xff09;、b&#xff08;表示…

深入探究QCheckBox的三種狀態及其用法

文章目錄 引言&#xff1a;三種狀態一、未選中狀態&#xff08;0&#xff09;&#xff1a;二、選中狀態&#xff08;2&#xff09;&#xff1a;三、部分選中狀態&#xff08;1&#xff09;&#xff1a; 判斷方法結論&#xff1a; 引言&#xff1a; QCheckBox是Qt框架中常用的復…

html css實現愛心

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* 愛心 */.lo…

修改Linux中SSH的端口

文章目錄 修改Linux中SSH的端口Linux中默認的ssh端口關閉SELinux測試新端口 修改Linux中SSH的端口 Linux中默認的ssh端口 使用root用戶操作 修改前先備份ssh_config cp /etc/ssh/sshd_config /etc/ssh/sshd_config_date "%Y%m%d%H%M%S"修改配置文件&#xff0c;找…

結構體的定義與賦值

1、結構體定義 首先定義一個學生結構體&#xff0c;如下所示&#xff1a; struct Student {int num;char name[32];char sex;int age; }; 接著在主函數中對學生進行聲明&#xff0c;如下所示&#xff1a; #include<iostream> using namespace std;struct Student {in…

2023Robocom省賽(本科組)

RC-u1 亞運獎牌榜 題目鏈接&#xff1a;PTA | 程序設計類實驗輔助教學平臺 (pintia.cn) 題目&#xff1a; 2022 年第 19 屆亞運會即將在杭州召開&#xff0c;杭州已經做好準備歡迎全亞洲的觀眾一同參與亞運盛會了&#xff01; 你正在開發一款跟亞運獎牌計算相關的 App。給定…

“深入探究JVM內部結構與工作原理:解析Java虛擬機“

標題&#xff1a;深入探究JVM內部結構與工作原理 摘要&#xff1a;本文將深入探究Java虛擬機&#xff08;JVM&#xff09;的內部結構與工作原理。我們將介紹JVM的基本組成部分&#xff0c;包括類加載器、運行時數據區和執行引擎。同時&#xff0c;我們將通過一個示例代碼來說明…

直接在html中引入Vue.js的cdn來實現一個簡單的上傳圖片組件

摘要 當使用 Vue.js 的 CDN 來實現一個簡單的上傳圖片組件時&#xff0c;你可以利用 Vue 的數據綁定和事件處理能力&#xff0c;結合 HTML 和 CSS&#xff0c;輕松地創建一個交互式的圖片上傳界面。以下是一個示例&#xff1a; 代碼結構 index.html <!DOCTYPE html> &…

LVS集群和分布式

LVS 一.集群和分布式概念 1.1 集群 在計算機領域&#xff0c;集群早在 1960 年就出現&#xff0c;隨著互聯網和計算機相關技術的發展&#xff0c;現在 集群這一技術已經在各大互聯網公司普及。 1.1.1 集群概念 計算機集群指一組通過計算機網絡連接的計算機&#xff0c;它們…