個人主頁:https://hellogod.cn
本文永久更新地址: 一時博客
1 引子
眾所周知,Linux系列知識極其重要,公司面試、實際開發都需要用到。最近看了一些資料之后,發覺自己很多地方沒有掌握到位,于是開始逐一查閱,順便整理了這個懶人版供大家參考。
每個需要詳細了解問題下面??需要的信息都給出了相應的參考鏈接,有些還配上了實際操作,相信你認真看完本文之后對于UNIX操作系統基礎能有一個更加清晰的認識。
2 正文
(17)在一個進程中,文件描述符的增長規律是怎樣的?例如,如果已經有0、1、2、6這樣幾個文件描述符,那么用open()返回的下一個文件描述符是什么?
(1)由open和openat函數返回的文件描述符一定是最小的未用描述符數值
(2)3
(3)詳細鏈接: Linux中的文件描述符與打開文件之間的關系
(18)什么是process id?父進程和子進程的pid之間有什么關系?(通常子進程的pid要大于父進程的pid)
- process id:進程標識符,unix系統中用來唯一標識每個進程,是一個非負整數。
- 子進程總是可以調用getppid來獲得父進程的pid,父進程在調用fork函數時可以獲得子進程的pid.
(19)什么是C語言程序的入口函數?在C Startup Routine(start.S)中接受的main函數原型是什么?
- Main函數。(當內核執行C程序時,在調用main前先調用一個特殊的啟動例程,可執行程序文件將此啟動例程設置為程序的起始地址。啟動例程從內核取得* 命令行參數和環境變量值,然后為按上述方式調用main函數做好安排。)
- Int main(int argc, char * argv[1]),argc是命令行參數的數目,argv是指向參數的各個指針所構成的數組。
(20)什么是系統調用?什么是C語言庫函數?它們之間有什么區別和聯系?
系統調用(system call),指運行在用戶空間的應用程序向操作系統內核請求某些服務的調用過程。 系統調用提供了用戶程序與操作系統之間的接口。一般來說,系統調用都在內核態執行。由于系統調用不考慮平臺差異性,由內核直接提供,因而移植性較差(幾乎無移植性)。
庫函數(library function),是由用戶或組織自己開發的,具有一定功能的函數集合,一般具有較好平臺移植性,通過庫文件(靜態庫或動態庫)向程序員提供功能性調用。程序員無需關心平臺差異,由庫來屏蔽平臺差異性。
系統調用實際上就是指最底層的一個調用,在linux程序設計里面就是底層調用的意思。面向的是硬件。而庫函數調用則面向的是應用開發的,相當于應用程序的api.
采用這樣的方式有很多種原因,第一:雙緩沖技術的實現。第二,可移植性。第三,底層調用本身的一些性能方面的缺陷。第四:讓api也可以有了級別和專門的工作面向。
(21)什么是inode?里面存放什么信息?文件的文件名存放在哪里?
inode(index node)就是索引節點,它用來存放檔案及目錄的基本信息,包含時間、檔名、使用者及群組等。inode信息就存儲在磁盤的某個分區上
inode 包含文件的元信息,具體來說有以下內容:
- 文件的字節數
- 文件擁有者的 User ID
- 文件的 Group ID
- 文件的讀、寫、執行權限
- 文件的時間戳,共有三個:ctime 指 inode 上一次變動的時間,mtime 指文件內容上一 次變動的時間,atime 指文件上一次打開的時間。
- 鏈接數,即有多少文件名指向這個 inode
- 文件數據 block 的位置
可以用 stat 命令,查看某個文件的 inode 信息: stat example.txt 總之,除了文件名以外的所有文件信息,都存在 inode 之中。
如下圖所示,文件名存在于目錄塊中。
*應@恒大大的小迷妹°添加: Linux 中文件名存在哪兒
(22)C程序的內存布局是怎樣的?從低地址到高地址依次存放哪些段?
正文段:由CPU執行的機器指令部分。
初始化數據段:程序中需明確地賦初值的變量。
非初始化數據段:在程序開始執行之前,內核將此段中的數據初始化為0或空指針。
棧:自動變量以及每次函數調用時所需保存的信息都存放在此段中。
堆:通常在堆中進行動態存儲分配。
從低地址到高地址依次存放正文,初始化的數據,未初始化的數據(bss),堆,棧。
參考鏈接:
(23)怎樣利用fork()、exec()、waitpid()來創建和控制進程?
1.fork 函數調用一次,但返回兩次。兩次返回的唯一區別是:子進程返回值為 0,而父進程的返回值是新子進程的進程 ID。fork 函數返回之后,子進程和父進程都各自繼續執行 fork 調用之后的指令。子進程是父進程的副本。例如,子進程獲得了父進程數據空間、堆和棧的副本。
2.當進程調用一種 exec 函數時,該進程執行的程序完全替換為新程序,而新程序則從其 main 函數開始執行。調用 exec 并沒有創建新進程,所以進程 ID 沒有改變,exec 只是用一個新的程序替換了當前進程的正文、數據、堆和棧段。
- waitpid 函數通過 pid 參數來控制父進程希望獲取特定進程的終止狀態信息,
? pid==-1:等待任一子進程,與 wait 函數等效。
? pid>0:等待其進程 ID 與 pid 相等的子進程。
? pid==0:等待其組 ID 等于調用進程組 ID 的任一子進程。(我們這里不學習進程組)
? pid<-1:等待其組 ID 等于 pid 絕對值的任一子進程。
waitpid 函數返回終止子進程的進程 ID。如果指定的進程或進程組不存在,或者參數 pid 指定的進程不是調用進程的子進程則都將出錯。
(24)什么是孤兒進程、什么是僵尸進程?它們有什么特點?怎樣避免產生過多僵尸進程?
孤兒進程:在其父進程執行完成或被終止后仍繼續運行的一類進程。這些孤兒進程將被init進程(進程號為1)所收養,并由init進程對它們完成狀態收集工作。
僵尸進程:在UNIX術語中,一個已經終止、但是其父進程尚未對其進行善后處理(獲取終止子進程的有關信息、釋放它仍占用的資源)的進程。
1.終止進程的父進程調用wait和waitpid
2.兩次調用fork()來使新創建的子進程變成孤兒進程,從而直接被1號進程收養。
(25)什么是前臺進程?什么是后臺進程?一個會話有幾個前臺進程組和幾個后臺進程組?
前臺進程:用戶使用的有控制終端的進程。
后臺進程:也稱守護進程,是運行在后臺的一種特殊進程。獨立于控制終端并且周期性地執行某種任務或者等待處理某些發生的事件。
一個會話中包含一個前臺進程組,一個或多個后臺進程組。
(26)C程序如何退出并返回操作系統?exit()函數和_exit()/_Exit()函數的差別在哪里?
進程的終止方式有 8 種,其中 5 種為正常終止,它們是
- 從 main 返回。
- 調用 exit。
- 調用_exit 或_Exit。
- 最后一個線程從其啟動例程返回。
- 最后一個線程調用pthread_exit。
另外三種為異常終止方式,它們是 - 調用 abort。
- 接到一個信號并終止。
- 最后一個線程對取消請求做出響應。
這三個函數用于正常終止一個程序:_exit 和_Exit 立即進入內核,exit 則先執行一些清理處理(包括調用執行各終止處理程序,關閉所有標準 I/O 流等),然后進入內核。
(27)exec函數族包含哪些具體的函數?其中execve是系統調用,其它都是普通函數。
UNIX 提供了 6 種不同的 exec 函數供我們使用。它們的原型如下所示,
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(cosnt char *filename, char *const argv[]);
6個函數的返回值:若出錯則返回-1,若成功則沒有返回值
可能很多人會覺得這六個函數太難記了。但是,我們仔細觀察會發現,這六個函數的命名是有一些規律的。
? 含有 l 和 v 的 exec 函數的參數表傳遞方式是不同的。
? 含有 e 結尾的 exec 函數會傳遞一個環境變量列表。
? 含有 p 結尾的 exec 函數取的是新程序的文件名作為參數,而其他exec 函數取的是新程序的路徑。
(28)什么是信號?SIGINT、SIGSTOP、SIGHUP、SIGALARM、SIGQUIT等信號是如何產生的?缺省的處理動作是什么?
在計算機科學中,信號是 Unix、類 Unix 以及其他 POSIX 兼容的操作系統中進程間通訊的一種有限制的方式。它是一種異步的通知機制,用來提醒進程一個事件已經發生。當一個信號發送給一個進程,操作系統中斷了進程正常的控制流程,此時,任何非原子操作都將被中斷。如果進程定義了信號的處理函數,那么它將被執行,否則就執行默認的處理函數。
SIGALRM 是鬧鐘信號,當由 alarm 函數設置的計時器超時后產生此信號。進程的信號屏蔽字復原為調用信號處理屏蔽字之前。
SIGQUIT,當用戶在終端上按退出鍵(一般是Ctrl+\)時,中斷驅動程序產生此信號,并發送給前臺進程組中的所有進程。
SIGSTOP,作業控制信號,用以停止一個進程,當用戶在終端上按掛起鍵(一般是Ctrl+Z)時,終端驅動程序產生此信號。
SIGHUP,如果終端接口檢測到一個連接斷開,則將此信號送給與該終端相關的控制進程(會話首進程)。
SIGINT,當用戶按中斷鍵(一般是Delete或Ctrl+C)時,終端驅動程序產生此信號并發送至前臺進程組中的每一個進程。
(29)什么是硬鏈接和軟鏈接(符號鏈接)?讀取軟連接的函數是什么?(readlink)
硬鏈接:通過 i 節點鏈接使多個目錄項指向同一個文件的這種鏈接類型。
符號鏈接:對一個文件的間接指針,一般用于將一個文件或整個目錄結構移到文件系統中的另一個位置。
readlink 函數打開符號鏈接本身,并讀取該鏈接中的內容(不是該鏈接所引用的文件的內容)。
(#include <unistd.h>
ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
返回值:若成功則返回讀到的字節數,若出錯則返回-1。
如果此函數成功執行,則返回讀入 buf 的字節數。在 buf 中返回的符號鏈接的內容不以 null 字符終止。)
(30)函數link()和unlink()的作用是什么?什么時候文件占用的磁盤空間才會真正被釋放掉?(兩個條件)
Link():創建一個指向現有文件的鏈接;unlink刪除一個現有的目錄項。
打開文件的進程個數為0;
文件的鏈接計數為0。
(31)什么是可重入函數?怎樣判斷一個函數是不是可重入函數?
什么是可重入函數和不可重入函數(轉) - Parry Nee - 博客園
一個可重入的函數簡單來說,就是:可以被中斷的函數。就是說,你可以在這個函數執行的任何時候中斷他的運行,在任務調度下去執行另外一段代 碼而不會出現什么錯誤。而不可重入的函數由于使用了一些系統資源,比如全局變量區,中斷向量表等等,所以他如果被中斷的話,可能出現問題,所以這類函數是 不能運行在多任務環境下的。
基本上下面的函數是不可重入的
(1)函數體內使用了靜態的數據結構;
(2)函數體內調用了malloc()或者free()函數;
(3)函數體內調用了標準I/O函數。
(32)什么是帶緩沖的輸出和不帶緩沖的輸出?當父進程的輸出緩沖區還未清空時,調用fork創建子進程,會出現什么情況?
- 不帶緩沖的輸出:在用戶的進程中對這這類的函數不會自動緩沖,每次執行就要進行一次系統調用,針對文件描述符操作。
- 帶緩沖的輸出:在系統調用的上一層多加了一個緩沖區,針對流來操作。
如果標準輸出連接到終端設備,則它是行緩沖的,否則它是全緩沖的。即以交互方式運行時,只得到父進程中printf輸出的行一次,原因是標準輸出緩沖區由換行符沖洗。但是當將標準輸出重定向到一個文件時,卻得到printf輸出兩次,原因是在fork之前調用了一次printf,但當調用fork時,該行數據仍在緩沖區中,然后在將父進程數據空間復制到子進程中時,該緩沖區數據也被復制到子進程中,此時父進程和子進程各自有了該行內容的緩沖區。
(33)要求詳細掌握用法的函數包括:fork、waitpid、execlp、open、read、write、lseek、close、malloc、free等。
(34)源碼:fork和printf相結合產生不同輸出的源代碼(forkflush.c)、用getpwnam演示的可重入函數的源代碼(reentrant.c)、創建/打開/讀/寫文件的源代碼(rwex.c等)。
3.寫在最后:
歡迎大家指正批評與交流,本博客將長期更新維護