起初學習UNIX環境下的編程覺得非常難,畢竟這是一個陌生的東西,但是工作後,照面打得多了,自然就熟悉了些,在此總結一些經驗。

其實要是寫了幾年代碼的人,回頭總結一下用代碼一般都做什麼事,會得到四個字母:CRUD,也就是增刪查改。

  • 後端程序員:對數據庫的增刪查改
  • 系統程序員:對OS數據結構的增刪查改
  • 嵌入式程序員:對硬件數據的增刪查改
  • 前端程序員:對JSON和用戶輸入的增刪查改

那既然大家歸根結底,都是增刪查改,學習UNIX是否可以從另外一個角度來看待呢?

開始之前

遠古時期,所有程序員都是直接操縱硬件的,但是這有很大的問題,你也想用100M內存,我也想用100M,但是一共只有100M,怎麼辦? 你也想播放音樂,我也想,又怎麼辦?你也想寫磁盤,我也想寫磁盤,怎麼辦?

除了上述衝突,更麻煩的是,所有人都要知道所有硬件怎麼操作,才能寫出一個程序,這太難了。於是有了操作系統,它幫我們屏蔽了所有的硬件設備的 區別,提供了統一的接口,並且抽象了一些東西,比如文件系統,通過文件系統,我們不再需要操心這個文件是寫到磁盤具體的哪個位置, 而只需要告訴操作系統:我要寫文件,寫什麼,寫多長即可。

這一切都是通過操作系統提供的系統調用(system call)來完成的。

我們還是要了解一下UNIX操作系統的結構:

可以看到,最內部的,就是內核,它幫我們處理各種各樣的硬件設備,然後抽象成一個簡單的概念。 比如我們不用擔心是HDD還是SSD,我們只管用文件系統,我們只需要調用操作文件的系統調用,操作系統會幫我們找到對應的磁盤位置並且把數據寫進去, 即使失敗了,也會返回一個錯誤告訴我們爲什麼。

稍往外層,就是系統調用,系統調用的外層有shell和library routines,另外applications也可以和它直接接觸,這是什麼意思呢?

shell可以簡單理解爲命令行,但是用熟了之後,應當知道,bash,zsh這類纔是shell,他封裝了很多系統調用,然後以命令或者圖形界面的方式提供 給用戶,比如Windows上,很多按鈕點一下,背後其實就調用了一些系統調用。 而library routines,比如glibc,幫我們封裝了系統調用,加了一些代碼,讓我們調用更加輕鬆,因爲我們的代碼不可能是通過執行各種命令行 來完成,那樣抗不住併發,所以我們得通過調用函數的方式, 我們可以直接調用系統調用,這就是圖中applications直接和system call接觸的那部分,也可以調用library routines,然後後者幫我們調用system call。

模塊分類

相信很多人畢業設計做的就是XXX管理系統,內核其實也是一個XXX管理系統,只不過它是多個管理系統的集合:

  • 文件管理系統
  • 用戶管理系統
  • 權限管理系統
  • 進程管理系統
  • 內存管理系統
  • 硬件設備管理系統

其實內核自己就可以做這些事情,但是因爲每個人每個公司的需求都不一樣,所以內核暴露了系統調用來讓程序員可以藉助它的能力, 做定製化操作。

內核爲了安全,會通過CPU的能力,將進程分爲兩個類別:

  • 在內核裏跑的
  • 在用戶空間跑的

顯然前者擁有更大的權利,幹啥都可以,後者的權力自然是受限的。

文件管理系統

我們知道,XXX管理系統的主要內容就是CRUD。我們來看看,我們自然是要:

  • 創建文件(增)
  • 刪除文件(刪)
  • 查找文件內容(查)
  • 更新文件內容(寫入)

由於除了文件,還有文件夾,所以它也有CRUD。由於UNIX中把一切都抽象成文件,因此,對他們也有CRUD,不過,幸好由於UNIX把一切都抽象成文件,因此 對一切文件,也就這麼幾個系統調用(其實是C庫封裝過後的庫函數,但是其底層是系統調用,後同):

creat
remove
close
read
write
lseek

當然,UNIX發展這麼久了,其歷史分支多的不得了,自然就會有分裂和不統一的情況,因此除了上述系統調用,你還會看到一些標新立異、特殊使用的,比如針對網絡的 recvsend ,沒有關係,理解一下,畢竟幾十年 的系統了。

說起文件,特別重要的一個概念就是文件描述符(file descriptor,簡稱fd)。這是啥呢?你看我們平時操作數據庫的每一行,是不是還有個Primary ID?對的,這玩意兒就跟它類似,用於定位一個文件,這樣大家都知道我們說的是哪個文件。

爲了高性能,我們的文件管理系統自然就要引入緩存,引入緩存,那就要引入把緩存寫到磁盤的操作,因此就有了:

sync
fsync
fdatasync

另外文件還有一些屬性什麼的,因此就有了:

stat
fstat
lstat

權限管理系統和用戶管理系統

UNIX是一個多用戶的系統,既然是多用戶,那就要做好權限控制,我不能隨意看別人的東西,別人也不能隨意看我的,要不然大家豈不是都在 裸奔了嗎?

首先要做的就是在文件系統裏引入權限控制,也就同時引入了用戶管理系統。

UNIX中,每個用戶都有自己的id,每個用戶都可以加入多個組,每個組也可以有多個用戶,因此UNIX把權限分爲三類:

  • 對本人的控制
  • 對本組的控制
  • 對其他人(也就是其他組)的控制

然後控制,又細分爲三個:

  • 可讀
  • 可寫
  • 可執行(對於文件來說是可執行,對於文件夾來說,就是是否可以列出其下文件)

由於UNIX設計的時候,內存都是很寶貴的(今天沒那麼貴了但是也還挺寶貴的),所以用位來保存權限,可讀、可寫、可執行分別是二進制的 00000100,00000010和00000001,因此對應10進制分別是4,2,1。

注意,4+2+1 = 7。

所以如果你給一個文件,權限爲777,那麼就是對本人可讀可寫可執行,對組內成員可讀可寫可執行,對其他所有人可讀可寫可執行。

如果是467,那就是對本人可讀,對組內成員可讀可寫,對其他人可讀可寫可執行。

以此類推。

當然,我們得有系統調用來對權限進行CRUD:

access
chmod

剛纔說了,有用戶和組這兩個概念。UNIX下,用戶信息存儲在 /etc/passwd 上,組的信息存儲在 /etc/group 上。

用戶有密碼,以前密碼也是存在 /etc/passwd 上,不過不太安全,後來就把密碼以更嚴格的文件權限存儲在 /etc/shadow 裏。

$ ll -h /etc/passwd /etc/shadow /etc/group
-rw-r--r-- 1 root root    910 Jun 12 15:29 /etc/group
-rw-r--r-- 1 root root   2.2K Jun 12 15:29 /etc/passwd
-rw-r----- 1 root shadow 1.3K Jun 12 15:29 /etc/shadow

進程管理系統和內存管理系統

我們的代碼,最終編譯爲程序,它就是一堆0和1,躺在磁盤裏。但是當它被操作系統加載,運行之後,在內存裏,就叫進程。當然,進程也得有一個唯一ID,我們管他叫 pid(process id)

進程和內存分不開,進程要申請內存、銷燬內存等,因此就有了這些CRUD:

malloc
free

至於改和查嘛,這是在代碼邏輯裏就做好了的,無需操心。

而對進程本身的一些增刪查改,則是對它的運行環境什麼的操作,比如:

  • getenv 獲取環境變量
  • getpid 獲取當前進程id
  • getppid 獲取父進程id
  • getrlimitsetrlimit 獲取和設置進程的一些資源限制
  • fork 創建新的進程
  • exec 替換當前進程所要執行的代碼
  • wait 等待子進程

進程管理本身就還包含很多東西,比如進程所屬的用戶和組,進程的資源限制,進程之間的通信,進程的優先級等等,每一個子系統都有它的CRUD。

後來又引入了線程這個概念,線程是CPU調度的最小單位,同一個進程內的多個線程, 共享所在進程裏的一些資源,對於這些東西,又有很多CRUD函數。同時還引入了併發和並行這兩個概念,因此也就有一些鎖、競爭等概念,所以又有一批 對應的CRUD函數。

硬件管理系統

程序要做的很重要的一件事情,其實是和其他進程通信,和其他硬件設備打交道。

同步I/O無法應對併發,因此就有了I/O多路複用,也就有了:

select
epoll
kqueue

還有POSIX規定的aio系列函數,這又是一堆CRUD。

總結

一個操作系統,要做的事情很多,直到今天,Linux內核也還在非常活躍的開發維護中,操作系統要做的事情遠遠不止上面所列的這麼 幾個管理系統,但是我們可以把操作系統要做的事情,分門別類來看,就會發現,原來它也是CRUD。當然,這個CRUD的技術含量,可就 比較高了。

相關文章