UNIX簡介
作業系統的狹義定義,是将作業系統定義為一種控制計算機資源,提供程式運作環境的軟體,通常我們稱之為核心,核心提供接口供上層應用調用,也叫做System Call(系統調用)。同時,為了友善應用程式使用核心,通常都會有公用函數庫,應用程式既可以使用系統調用,也可以使用公用函數庫。系統調用和公用函數庫實際上并不是同一個東西,但是對于開發者來說,可以當作同一個層,都可以使用C函數來調用。再向上,就是shell終端,作為人機互動部分,最外層則是應用程式。
而從廣義上來說,作業系統就是一個包含了核心和必備系統軟體的集合,這些軟體是支援一個系統正常運轉使用、人機互動的最小要求。
目前來說,已經不存在真正的Unix系統了,因為自從AT&T公司封閉Unix源代碼,Unix的變種分支就出現了很多,其中學院派BSD,商業SystemV,和開源的Linux最重走到了最後。其中,BSD原先是基于AT&T開放代碼建構,後來Unix實驗室被賣給了Novell,Novell授權BSD開發Unix,但是去除了源自AT&T的源代碼,最終形成了BSD-4.4 Lite,也是目前很多類Unix作業系統的基石。商業SystemV則是AT&T聯合許多公司,用于解決Unix混亂的商業版本,是以後來的很多商業Unix都是基于SystemV release4版本,而後Unix被賣給Novell,最終到了X/OPEN Consortium,即後來的Open Group。
檔案和目錄
#include "apue.h"
#include <dirent.h>
int
main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;
if (argc != )
err_quit("usage: ls directory_name");
if ((dp = opendir(argv[])) == NULL)
err_sys("can't open %s", argv[]);
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);
closedir(dp);
exit();
}
函數opendir()打開我們傳入的目錄,傳回的是目錄流,函數readdir()讀取目錄流資訊,readdir()傳回參數dir目錄流的下個目錄進入點。
關于APUE書中代碼的運作,作者給出的源代碼中有些不太友善,要根據英文意思去看代碼,沒有唯一的檔案名與之對應,但這一點來看
輸入輸出
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > )
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < )
err_sys("read error");
exit();
}
這個例子讓我看到時鬧了個笑話:
第三版書中有這麼一句話:
若以下列方式執行該程式:
./a.out < infile >outfile
我當時在想<>是什麼奇怪的文法,難道是可以省略檔案infile的意思,直到我在心愛的mac上運作了這個程式。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuQXYjlXbvwVbvNmLuRGZ19Gbj5CdrJmL3R3d3UDe2V3bvw1LcpDc0RHaiojIsJye.png)
原來這不就是重定向文法嗎?
shell中>表示輸出重定向 <表示輸入重定向
./a.out < infile >outfile的意思是标準輸入重定向到檔案infile 标準輸出重定向到檔案outfile,實作了檔案infile拷貝到outfile.
檔案描述符在C語言内部是一個非負整數,核心用其來标示一個程序通路的檔案,每個程序都維護自己的檔案描述符,按照标準規定,當一個程序運作時,都預設打開三個檔案描述符,即标準輸入、标準輸出和标準錯誤,正常情況下,這三個檔案都指向終端輸出,但是在終端可以使用重定向的方式将這三個檔案描述符指向不同的地方。我們可以檢視一下系統頭檔案
vim /usr/include/unistd.h
找到如下内容:
#define STDIN_FILENO 0 /* standard input file descriptor */
#define STDOUT_FILENO 1 /* standard output file descriptor */
#define STDERR_FILENO 2 /* standard error file descriptor */
實際上0、1、2就預設已經被使用了,如果我們在此基礎上新打開一個檔案,實際上是增加在3的位置,而且每個程序都有0、1、2的檔案描述符。
程式與程序
程式是一段放置于磁盤上的二進制代碼,核心使用exec函數族來講程序讀入記憶體,并且執行程式,在記憶體中運作的程式執行個體被稱為程序,Unix标準要求每個程序都有唯一表示符(process ID即pid),pid是一個非負整數。
#include "apue.h"
int
main(void)
{
printf("hello world from process ID %ld\n", (long)getpid());
exit();
}
getpid函數得到程序ID,getpid傳回一個pid_t資料類型,雖然大多數程序ID可以用整數表示,但用長整型可以提高移植性。
程序控制有主要三個函數:fork、exec和waitpid。(exec函數有7種變體,但是一般統稱exec函數)
#include "apue.h"
#include <sys/wait.h>
int
main(void)
{
char buf[MAXLINE]; /* from apue.h */
pid_t pid;
int status;
printf("%% "); /* print prompt (printf requires %% to print %) */
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - ] == '\n')
buf[strlen(buf) - ] = ; /* replace newline with null */
if ((pid = fork()) < ) {
err_sys("fork error");
} else if (pid == ) { /* child */
execlp(buf, buf, (char *));
err_ret("couldn't execute: %s", buf);
exit();
}
/* parent */
if ((pid = waitpid(pid, &status, )) < )
err_sys("waitpid error");
printf("%% ");
}
exit();
}
在這個程式裡使用了标準I/O函數fgets從标準輸入讀取一行,當輸入檔案結束符Ctrl+D時候,fgets傳回一個null指針,然後就會直接執行exit(0);讓程序退出
fgets每次讀取的一行都以換行符終止,是以buf最後兩個字元就是’\n’和’\0’,但是execlp函數要求參數必須以’\0’結尾,不需要’\n’換行符,是以我們使用’\0’字元先替換了’\n’,讓execlp函數能順利執行
調用fork函數建立一個新程序,新程序是父程序的副本,fork對父程序傳回子程序的pid,對子程序則傳回整數0,并且子程序是完全複制父程序的目前記憶體空間,是以子程序一開始執行的代碼就是父程序正在執行的代碼,是以說fork函數被調用一次(在父程序調用),但傳回兩次(父程序和子程序都得到傳回值)
根據fork函數的傳回值判斷目前程序是子程序還是父程序,在子程序中,調用execlp執行指令,使用新的程式檔案替換了原先子程序的程式檔案。而父程序則使用waitpid等待子程序的終止,當一切執行完畢,則列印出新的提示符%
在上面的例子中我們如果按下中斷鍵,則執行此程式的程序終止。産生這種後果的原因是:對于此信号(SIGINT)系統預設動作是終止程序。在下面的例子中我們調用signal()函數捕獲此信号,列印一條資訊。
#include "apue.h"
#include <sys/wait.h>
static void sig_int(int); /* our signal-catching function */
int
main(void)
{
char buf[MAXLINE]; /* from apue.h */
pid_t pid;
int status;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal error");
printf("%% "); /* print prompt (printf requires %% to print %) */
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - ] == '\n')
buf[strlen(buf) - ] = ; /* replace newline with null */
if ((pid = fork()) < ) {
err_sys("fork error");
} else if (pid == ) { /* child */
execlp(buf, buf, (char *));
err_ret("couldn't execute: %s", buf);
exit();
}
/* parent */
if ((pid = waitpid(pid, &status, )) < )
err_sys("waitpid error");
printf("%% ");
}
exit();
}
void
sig_int(int signo)
{
printf("interrupt\n%% ");
}