嵌入式系統開發:基於Linux學習筆記整理

冷色調的夏天發表於2020-09-28

Linux命令操作部分

Ubuntu虛擬機器使用

快照

拍攝快照是為了方便還原虛擬機器,因為虛擬機器(Virtual Machine)是虛擬出來的出來的一臺物理計算機,如果你在實驗中操作不當或者其他原因導致虛擬機器無法正常使用,如果你之前打過快照(Snapshot),那麼你就可以很方便的恢復到上一次打快照的地方

掛起虛擬機器

掛起虛擬機器的作用是為了下次更加方便虛擬機器的開啟,如果直接關閉虛擬機器的話虛擬機器就有可能出一些問題,因此不建議直接關閉虛擬機器
在這裡插入圖片描述

百度語法(論文搜尋)

這些百度語法在百度搜尋引擎中可以更快的搜尋出你想要的東西

用途:當你要寫一些論文什麼的就可以這樣搜尋查詢,可以很大機率避免和別人重複

例如 你想要找一些doc文件 ,那你可以在百度的搜尋框中鍵入 linux嵌入式程式設計 filetype:doc 就可以搜尋出來所有doc的文件而略過其他很多亂七八糟的東西
在這裡插入圖片描述

如果你想要搜尋另外其他指定型別的文件而不想記這些複雜的命令可以訪問

https://www.baidu.com/gaoji/advanced.html

該網址可以讓你使用百度的高階搜尋
在這裡插入圖片描述

檢視更多幫助

https://jingyan.baidu.com/article/d621e8dae7593c2864913f7b.html

Linux常用命令(快捷鍵)

ls命令

ls命令用來顯示目標列表,在Linux中是使用率較高的命令。ls命令的輸出資訊可以進行彩色加亮顯示,以分割槽不同型別的檔案

選項:
-a:顯示所有檔案及目錄(ls內定將檔案名或目錄名稱為“.”的視為影藏,不會列出);
-A:顯示除影藏檔案“.”和“..”以外的所有檔案列表;
-C:多列顯示輸出結果。這是預設選項;
-l:與“-C”選項功能相反,所有輸出資訊用單列格式輸出,不輸出為多列;
-F:在每個輸出項後追加檔案的型別識別符號,具體含義:“*”表示具有可執行許可權的普通檔案,“/”表示目錄,“@”表示符號連結,“|”表示命令管道FIFO,“=”表示sockets套接字。當檔案為普通檔案時,不輸出任何識別符號;
-b:將檔案中的不可輸出的字元以反斜線“”加字元編碼的方式輸出;
-c:與“-lt”選項連用時,按照檔案狀態時間排序輸出目錄內容,排序的依據是檔案的索引節點中的ctime欄位。與“-l”選項連用時,則排序的一句是檔案的狀態改變時間;
-d:僅顯示目錄名,而不顯示目錄下的內容列表。顯示符號連結檔案本身,而不顯示其所指向的目錄列表;
-f:此引數的效果和同時指定“aU”引數相同,並關閉“lst”引數的效果;
-i:顯示檔案索引節點號(inode)。一個索引節點代表一個檔案;
--file-type:與“-F”選項的功能相同,但是不顯示“*”;
-k:以KB(千位元組)為單位顯示檔案大小;
-l:以長格式顯示目錄下的內容列表。輸出的資訊從左到右依次包括檔名,檔案型別、許可權模式、硬連線數、所有者、組、檔案大小和檔案的最後修改時間等;
-m:用“,”號區隔每個檔案和目錄的名稱;
-n:以使用者識別碼和群組識別碼替代其名稱;
-r:以檔名反序排列並輸出目錄內容列表;
-s:顯示檔案和目錄的大小,以區塊為單位;
-t:用檔案和目錄的更改時間排序;
-L:如果遇到性質為符號連結的檔案或目錄,直接列出該連結所指向的原始檔案或目錄;
-R:遞迴處理,將指定目錄下的所有檔案及子目錄一併處理;
--full-time:列出完整的日期與時間;
--color[=WHEN]:使用不同的顏色高亮顯示不同型別的。

示例:

顯示當前目錄下非影藏檔案與目錄

[root@localhost ~]# ls
anaconda-ks.cfg  install.log  install.log.syslog  satools

顯示當前目錄下包括影藏檔案在內的所有檔案列表

[root@localhost ~]# ls -a
.   anaconda-ks.cfg  .bash_logout   .bashrc  install.log         .mysql_history  satools  .tcshrc   .vimrc
..  .bash_history    .bash_profile  .cshrc   install.log.syslog  .rnd            .ssh     .viminfo

顯示檔案的inode資訊

索引節點(index inode簡稱為“inode”)是Linux中一個特殊的概念,具有相同的索引節點號的兩個文字本質上是同一個檔案(除檔名不同外)。

[root@localhost ~]# ls -i -l anaconda-ks.cfg install.log
2345481 -rw------- 1 root root   859 Jun 11 22:49 anaconda-ks.cfg
2345474 -rw-r--r-- 1 root root 13837 Jun 11 22:49 install.log

按修改時間列出檔案和資料夾詳細資訊

[root@localhost /]# ls -ltr

total 254
drwxr-xr-x   2 root root  4096 Nov  8  2010 misc
drwxr-xr-x   2 root root  4096 May 11  2011 srv
drwxr-xr-x   2 root root  4096 May 11  2011 selinux
drwxr-xr-x   2 root root  4096 May 11  2011 opt

cd命令

cd命令用來切換工作目錄至dirname。 其中dirName表示法可為絕對路徑或相對路徑。若目錄名稱省略,則變換至使用者的home directory(也就是剛login時所在的目錄)。另外,~也表示為home directory的意思,.則是表示目前所在的目錄,..則表示目前目錄位置的上一層目錄。

cd / #根目錄
cd    #進入使用者主目錄;
cd ~  #進入使用者主目錄;
cd -  #返回進入此目錄之前所在的目錄;
cd ..  #返回上級目錄(若當前目錄為“/“,則執行完後還在“/";".."為上級目錄的意思);
cd ../..  #返回上兩級目錄;
cd !$  #把上個命令的引數作為cd引數使用。

cp命令

cp命令用來將一個或多個原始檔或者目錄複製到指定的目的檔案或目錄。它可以將單個原始檔複製成一個指定檔名的具體的檔案或一個已經存在的目錄下。cp命令還支援同時複製多個檔案,當一次複製多個檔案時,目標檔案引數必須是一個已經存在的目錄,否則將出現錯誤

選項:
-a:此引數的效果和同時指定"-dpR"引數相同;
-d:當複製符號連線時,把目標檔案或目錄也建立為符號連線,並指向與原始檔或目錄連線的原始檔案或目錄;
-f:強行復制檔案或目錄,不論目標檔案或目錄是否已存在;
-i:覆蓋既有檔案之前先詢問使用者;
-l:對原始檔建立硬連線,而非複製檔案;
-p:保留原始檔或目錄的屬性;
-R/r:遞迴處理,將指定目錄下的所有檔案與子目錄一併處理;
-s:對原始檔建立符號連線,而非複製檔案;
-u:使用這項引數後只會在原始檔的更改時間較目標檔案更新時或是名稱相互對應的目標檔案並不存在時,才複製檔案;
-S:在備份檔案時,用指定的字尾“SUFFIX”代替檔案的預設字尾;
-b:覆蓋已存在的檔案目標前將目標檔案備份;
-v:詳細顯示命令執行的操作。

示例:

如果把一個檔案複製到一個目標檔案中,而目標檔案已經存在,那麼,該目標檔案的內容將被破壞。此命令中所有引數既可以是絕對路徑名,也可以是相對路徑名。通常會用到點.或點點..的形式。例如,下面的命令將指定檔案複製到當前目錄下:

cp ../mary/homework/assign .

所有目標檔案指定的目錄必須是己經存在的,cp命令不能建立目錄。如果沒有檔案複製的許可權,則系統會顯示出錯資訊。

將檔案file複製到目錄/usr/men/tmp下,並改名為file1

cp file /usr/men/tmp/file1

將目錄/usr/men下的所有檔案及其子目錄複製到目錄/usr/zh

cp -r /usr/men /usr/zh

互動式地將目錄/usr/men中的以m打頭的所有.c檔案複製到目錄/usr/zh

cp -i /usr/men m*.c /usr/zh

我們在Linux下使用cp命令複製檔案時候,有時候會需要覆蓋一些同名檔案,覆蓋檔案的時候都會有提示:需要不停的按Y來確定執行覆蓋。檔案數量不多還好,但是要是幾百個估計按Y都要吐血了

cp aaa/* /bbb
複製目錄aaa下所有到/bbb目錄下,這時如果/bbb目錄下有和aaa同名的檔案,需要按Y來確認並且會略過aaa目錄下的子目錄。

cp -r aaa/* /bbb
這次依然需要按Y來確認操作,但是沒有忽略子目錄。

cp -r -a aaa/* /bbb
依然需要按Y來確認操作,並且把aaa目錄以及子目錄和檔案屬性也傳遞到了/bbb。

\cp -r -a aaa/* /bbb
成功,沒有提示按Y、傳遞了目錄屬性、沒有略過目錄。

mv命令

mv命令用來對檔案或目錄重新命名,或者將檔案從一個目錄移到另一個目錄中。source表示原始檔或目錄,target表示目標檔案或目錄。如果將一個檔案移到一個已經存在的目標檔案中,則目標檔案的內容將被覆蓋。

mv命令可以用來將原始檔移至一個目標檔案中,或將一組檔案移至一個目標目錄中。原始檔被移至目標檔案有兩種不同的結果:

  1. 如果目標檔案是到某一目錄檔案的路徑,原始檔會被移到此目錄下,且檔名不變。
  2. 如果目標檔案不是目錄檔案,則原始檔名(只能有一個)會變為此目標檔名,並覆蓋己存在的同名檔案。如果原始檔和目標檔案在同一個目錄下,mv的作用就是改檔名。當目標檔案是目錄檔案時,原始檔或目錄引數可以有多個,則所有的原始檔都會被移至目標檔案中。所有移到該目錄下的檔案都將保留以前的檔名。

注意事項:mv與cp的結果不同,mv好像檔案“搬家”,檔案個數並未增加。而cp對檔案進行復制,檔案個數增加了。

選項:
--backup=<備份模式>:若需覆蓋檔案,則覆蓋前先行備份;
-b:當檔案存在時,覆蓋前,為其建立一個備份;
-f:若目標檔案或目錄與現有的檔案或目錄重複,則直接覆蓋現有的檔案或目錄;
-i:互動式操作,覆蓋前先行詢問使用者,如果原始檔與目標檔案或目標目錄中的檔案同名,則詢問使用者是否覆蓋目標檔案。使用者輸入”y”,表示將覆蓋目標檔案;輸入”n”,表示取消對原始檔的移動。這樣可以避免誤將檔案覆蓋。
--strip-trailing-slashes:刪除原始檔中的斜槓“/”;
-S<字尾>:為備份檔案指定字尾,而不使用預設的字尾;
--target-directory=<目錄>:指定原始檔要移動到目標目錄;
-u:當原始檔比目標檔案新或者目標檔案不存在時,才執行移動操作。

示例:

將檔案ex3改名為new1

mv ex3 new1

將目錄/usr/men中的所有檔案移到當前目錄(用.表示)中:

mv /usr/men/* .

mkdir命令

mkdir命令用來建立目錄。該命令建立由dirname命名的目錄。如果在目錄名的前面沒有加任何路徑名,則在當前目錄下建立由dirname指定的目錄;如果給出了一個已經存在的路徑,將會在該目錄下建立一個指定的目錄。在建立目錄時,應保證新建的目錄與它所在目錄下的檔案沒有重名。

注意:在建立檔案時,不要把所有的檔案都存放在主目錄中,可以建立子目錄,通過它們來更有效地組織檔案。最好採用前後一致的命名方式來區分檔案和目錄。例如,目錄名可以以大寫字母開頭,這樣,在目錄列表中目錄名就出現在前面。

在一個子目錄中應包含型別相似或用途相近的檔案。例如,應建立一個子目錄,它包含所有的資料庫檔案,另有一個子目錄應包含電子表格檔案,還有一個子目錄應包含文書處理文件,等等。目錄也是檔案,它們和普通檔案一樣遵循相同的命名規則,並且利用全路徑可以唯一地指定一個目錄。

選項:
-Z:設定安全上下文,當使用SELinux時有效;
-m<目標屬性>或--mode<目標屬性>建立目錄的同時設定目錄的許可權;
-p或--parents 若所要建立目錄的上層目錄目前尚未建立,則會一併建立上層目錄;
--version 顯示版本資訊。

示例:

在目錄/sang/test下建立子目錄test1,並且只有檔案主有讀、寫和執行許可權,其他人無權訪問

mkdir -m 700 /sang/test/test1

在當前目錄中建立bin和bin下的os_1目錄,許可權設定為檔案主可讀、寫、執行,同組使用者可讀和執行,其他使用者無權訪問

mkdir -p -m 750 bin/os_1

touch命令

touch命令有兩個功能:一是用於把已存在檔案的時間標籤更新為系統當前的時間(預設方式),它們的資料將原封不動地保留下來;二是用來建立新的空檔案

選項:
-a:或--time=atime或--time=access或--time=use  只更改存取時間;
-c:或--no-create  不建立任何檔案;
-d:<時間日期> 使用指定的日期時間,而非現在的時間;
-f:此引數將忽略不予處理,僅負責解決BSD版本touch指令的相容性問題;
-m:或--time=mtime或--time=modify  只更該變動時間;
-r:<參考檔案或目錄>  把指定檔案或目錄的日期時間,統統設成和參考檔案或目錄的日期時間相同;
-t:<日期時間>  使用指定的日期時間,而非現在的時間;
--help:線上幫助;
--version:顯示版本資訊。

示例:

touch ex2

在當前目錄下建立一個空檔案ex2,然後,利用ls -l命令可以發現檔案ex2的大小為0,表示它是空檔案。

rm命令

rm命令可以刪除一個目錄中的一個或多個檔案或目錄,也可以將某個目錄及其下屬的所有檔案及其子目錄均刪除掉。對於連結檔案,只是刪除整個連結檔案,而原有檔案保持不變。

注意:使用rm命令要格外小心。因為一旦刪除了一個檔案,就無法再恢復它。所以,在刪除檔案之前,最好再看一下檔案的內容,確定是否真要刪除。rm命令可以用-i選項,這個選項在使用副檔名字元刪除多個檔案時特別有用。使用這個選項,系統會要求你逐一確定是否要刪除。這時,必須輸入y並按Enter鍵,才能刪除檔案。如果僅按Enter鍵或其他字元,檔案不會被刪除。

選項:
-d:直接把欲刪除的目錄的硬連線資料刪除成0,刪除該目錄;
-f:強制刪除檔案或目錄;
-i:刪除已有檔案或目錄之前先詢問使用者;
-r或-R:遞迴處理,將指定目錄下的所有檔案與子目錄一併處理;
--preserve-root:不對根目錄進行遞迴操作;
-v:顯示指令的詳細執行過程。

示例:

互動式刪除當前目錄下的檔案test和example

rm -i test example
Remove test ?n(不刪除檔案test)
Remove example ?y(刪除檔案example)

刪除當前目錄下除隱含檔案外的所有檔案和子目錄

# rm -r *

應注意,這樣做是非常危險的!

pwd命令

pwd命令以絕對路徑的方式顯示使用者當前工作目錄。命令將當前目錄的全路徑名稱(從根目錄)寫入標準輸出。全部目錄使用/分隔。第一個/表示根目錄,最後一個目錄是當前目錄。執行pwd命令可立刻得知您目前所在的工作目錄的絕對路徑名稱。

示例:

[root@localhost ~]# pwd
/root

gcc命令

選項:
-o:指定生成的輸出檔案;
-E:僅執行編譯預處理;
-S:將C程式碼轉換為彙編程式碼;
-wall:顯示警告資訊;
-c:僅執行編譯操作,不進行連線操作。

示例:

常用編譯命令選項

假設源程式檔名為test.c

無選項編譯連結

gcc test.c

將test.c預處理、彙編、編譯並連結形成可執行檔案。這裡未指定輸出檔案,預設輸出為a.out。

選項 -o

gcc test.c -o test

將test.c預處理、彙編、編譯並連結形成可執行檔案test。-o選項用來指定輸出檔案的檔名。

選項 -E

gcc -E test.c -o test.i

將test.c預處理輸出test.i檔案。

選項 -S

gcc -S test.i

將預處理輸出檔案test.i彙編成test.s檔案。

選項 -c

gcc -c test.s

將彙編輸出檔案test.s編譯輸出test.o檔案。

無選項鍊接

gcc test.o -o test

將編譯輸出檔案test.o連結成最終可執行檔案test。

選項 -O

gcc -O1 test.c -o test

使用編譯優化級別1編譯程式。級別為1~3,級別越大優化效果越好,但編譯時間越長。

多原始檔的編譯方法

如果有多個原始檔,基本上有兩種編譯方法:

假設有兩個原始檔為test.c和testfun.c

多個檔案一起編譯

gcc testfun.c test.c -o test

將testfun.c和test.c分別編譯後連結成test可執行檔案。

分別編譯各個原始檔,之後對編譯後輸出的目標檔案連結。

gcc -c testfun.c    #將testfun.c編譯成testfun.o
gcc -c test.c       #將test.c編譯成test.o
gcc -o testfun.o test.o -o test    #將testfun.o和test.o連結成test

以上兩種方法相比較,第一中方法編譯時需要所有檔案重新編譯,而第二種方法可以只重新編譯修改的檔案,未修改的檔案不用重新編譯。

gdb命令

gdb命令包含在GNU的gcc開發套件中,是功能強大的程式偵錯程式。

命令解釋示例
file <檔名>載入被除錯的可執行程式檔案。 因為一般都在被除錯程式所在目錄下執行GDB,因而文字名不需要帶路徑。(gdb) file gdb-sample
rRun的簡寫,執行被除錯的程式。 如果此前沒有下過斷點,則執行完整個程式;如果有斷點,則程式暫停在第一個可用斷點處。(gdb) r
cContinue的簡寫,繼續執行被除錯程式,直至下一個斷點或程式結束。(gdb) c
b <行號> b <函式名稱> b *<函式名稱> b *<程式碼地址> d [編號]b: Breakpoint的簡寫,設定斷點。兩可以使用“行號”“函式名稱”“執行地址”等方式指定斷點位置。 其中在函式名稱前面加“*”符號表示將斷點設定在“由編譯器生成的prolog程式碼處”。如果不瞭解彙編,可以不予理會此用法。 d: Delete breakpoint的簡寫,刪除指定編號的某個斷點,或刪除所有斷點。斷點編號從1開始遞增。(gdb) b 8 (gdb) b main (gdb) b *main (gdb) b *0x804835c (gdb) d
s, ns: 執行一行源程式程式碼,如果此行程式碼中有函式呼叫,則進入該函式; n: 執行一行源程式程式碼,此行程式碼中的函式呼叫也一併執行。 s 相當於其它偵錯程式中的“Step Into (單步跟蹤進入)”; n 相當於其它偵錯程式中的“Step Over (單步跟蹤)”。 這兩個命令必須在有原始碼除錯資訊的情況下才可以使用(GCC編譯時使用“-g”引數)。(gdb) s (gdb) n
si, nisi命令類似於s命令,ni命令類似於n命令。所不同的是,這兩個命令(si/ni)所針對的是彙編指令,而s/n針對的是原始碼。(gdb) si (gdb) ni
p <變數名稱>Print的簡寫,顯示指定變數(臨時變數或全域性變數)的值。(gdb) p i (gdb) p nGlobalVar
display … undisplay <編號>display,設定程式中斷後欲顯示的資料及其格式。 例如,如果希望每次程式中斷後可以看到即將被執行的下一條彙編指令,可以使用命令 “display /i $pc” 其中 $pc 代表當前彙編指令,/i 表示以十六進行顯示。當需要關心彙編程式碼時,此命令相當有用。 undispaly,取消先前的display設定,編號從1開始遞增。(gdb) display /i $pc (gdb) undisplay 1
iinfo的簡寫,用於顯示各類資訊,詳情請查閱“help i”。(gdb) i r
qQuit的簡寫,退出GDB除錯環境。(gdb) q
help [命令名稱]GDB幫助命令,提供對GDB名種命令的解釋說明。 如果指定了“命令名稱”引數,則顯示該命令的詳細說明;如果沒有指定引數,則分類顯示所有GDB命令,供使用者進一步瀏覽和查詢。(gdb) help
選項:
-cd:設定工作目錄;
-q:安靜模式,不列印介紹資訊和版本資訊;
-d:新增檔案查詢路徑;
-x:從指定檔案中執行GDB指令;
-s:設定讀取的符號表檔案。

示例:

以下是linux下dgb除錯的一個例項,先給出一個示例用的小程式,C語言程式碼:

#include <stdio.h>
int nGlobalVar = 0;

int tempFunction(int a, int b)
{
    printf("tempFunction is called, a = %d, b = %d /n", a, b);
    return (a + b);
}

int main()
{
    int n;
        n = 1;
        n++;
        n--;

        nGlobalVar += 100;
        nGlobalVar -= 12;

    printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);

        n = tempFunction(1, 2);
    printf("n = %d", n);

    return 0;
}

請將此程式碼複製出來並儲存到檔案 gdb-sample.c 中,然後切換到此檔案所在目錄,用GCC編譯之:

gcc gdb-sample.c -o gdb-sample -g

在上面的命令列中,使用 -o 引數指定了編譯生成的可執行檔名為 gdb-sample,使用引數 -g 表示將原始碼資訊編譯到可執行檔案中。如果不使用引數 -g,會給後面的GDB除錯造成不便。當然,如果我們沒有程式的原始碼,自然也無從使用 -g 引數,除錯/跟蹤時也只能是彙編程式碼級別的除錯/跟蹤。

下面“gdb”命令啟動GDB,將首先顯示GDB說明,不管它:

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb)

上面最後一行“(gdb)”為GDB內部命令引導符,等待使用者輸入GDB命令。

下面使用“file”命令載入被除錯程式 gdb-sample(這裡的 gdb-sample 即前面 GCC 編譯輸出的可執行檔案):

(gdb) file gdb-sample
Reading symbols from gdb-sample...done.

上面最後一行提示已經載入成功。

下面使用“r”命令執行(Run)被除錯檔案,因為尚未設定任何斷點,將直接執行到程式結束:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

下面使用“b”命令在 main 函式開頭設定一個斷點(Breakpoint):

(gdb) b main
Breakpoint 1 at 0x804835c: file gdb-sample.c, line 19.

上面最後一行提示已經成功設定斷點,並給出了該斷點資訊:在原始檔 gdb-sample.c 第19行處設定斷點;這是本程式的第一個斷點(序號為1);斷點處的程式碼地址為 0x804835c(此值可能僅在本次除錯過程中有效)。回過頭去看原始碼,第19行中的程式碼為“n = 1”,恰好是 main 函式中的第一個可執行語句(前面的“int n;”為變數定義語句,並非可執行語句)。

再次使用“r”命令執行(Run)被除錯程式:

(gdb) r
Starting program: /home/liigo/temp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;

程式中斷在gdb-sample.c第19行處,即main函式是第一個可執行語句處。

上面最後一行資訊為:下一條將要執行的原始碼為“n = 1;”,它是原始碼檔案gdb-sample.c中的第19行。

下面使用“s”命令(Step)執行下一行程式碼(即第19行“n = 1;”):

(gdb) s
20 n++;

上面的資訊表示已經執行完“n = 1;”,並顯示下一條要執行的程式碼為第20行的“n++;”。

既然已經執行了“n = 1;”,即給變數 n 賦值為 1,那我們用“p”命令(Print)看一下變數 n 的值是不是 1 :

(gdb) p n
$1 = 1

果然是 1。($1大致是表示這是第一次使用“p”命令——再次執行“p n”將顯示“$2 = 1”——此資訊應該沒有什麼用處。)

下面我們分別在第26行、tempFunction 函式開頭各設定一個斷點(分別使用命令“b 26”“b tempFunction”):

(gdb) b 26
Breakpoint 2 at 0x804837b: file gdb-sample.c, line 26.
(gdb) b tempFunction
Breakpoint 3 at 0x804832e: file gdb-sample.c, line 12.

使用“c”命令繼續(Continue)執行被除錯程式,程式將中斷在第二 個斷點(26行),此時全域性變數 nGlobalVar 的值應該是 88;再一次執行“c”命令,程式將中斷於第三個斷點(12行,tempFunction 函式開頭處),此時tempFunction 函式的兩個引數 a、b 的值應分別是 1 和 2:

(gdb) c
Continuing.

Breakpoint 2, main () at gdb-sample.c:26
26 printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
(gdb) p nGlobalVar
$2 = 88
(gdb) c
Continuing.
n = 1, nGlobalVar = 88

Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:12
12 printf("tempFunction is called, a = %d, b = %d /n", a, b);
(gdb) p a
$3 = 1
(gdb) p b
$4 = 2

上面反饋的資訊一切都在我們預料之中~~

再一次執行“c”命令(Continue),因為後面再也沒有其它斷點,程式將一直執行到結束:

(gdb) c
Continuing.
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

有時候需要看到編譯器生成的彙編程式碼,以進行彙編級的除錯或跟蹤,又該如何操作呢?

這就要用到display命令“display /i $pc”了(此命令前面已有詳細解釋):

(gdb) display /i $pc
(gdb)

此後程式再中斷時,就可以顯示出彙編程式碼了:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

看到了彙編程式碼,“n = 1;”對應的彙編程式碼是“movl $0x1,0xfffffffc(%ebp)”。

並且以後程式每次中斷都將顯示下一條彙編指定(“si”命令用於執行一條彙編程式碼——區別於“s”執行一行C程式碼):

(gdb) si
20 n++;
1: x/i $pc 0x8048363 <main+23>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x08048366 20 n++;
1: x/i $pc 0x8048366 <main+26>: incl (%eax)
(gdb) si
21 n--;
1: x/i $pc 0x8048368 <main+28>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x0804836b 21 n--;
1: x/i $pc 0x804836b <main+31>: decl (%eax)
(gdb) si
23 nGlobalVar += 100;
1: x/i $pc 0x804836d <main+33>: addl $0x64,0x80494fc

接下來我們試一下命令“b *<函式名稱>”。

為了更簡明,有必要先刪除目前所有斷點(使用“d”命令——Delete breakpoint):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb)

當被詢問是否刪除所有斷點時,輸入“y”並按Enter鍵即可。

下面使用命令“b *main”在 main 函式的 prolog 程式碼處設定斷點(prolog、epilog,分別表示編譯器在每個函式的開頭和結尾自行插入的程式碼):

(gdb) b *main
Breakpoint 4 at 0x804834c: file gdb-sample.c, line 17.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 4, main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834c <main>: push %ebp
(gdb) si
0x0804834d 17 {
1: x/i $pc 0x804834d <main+1>: mov %esp,%ebp
(gdb) si
0x0804834f in main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834f <main+3>: sub $0x8,%esp
(gdb) si
0x08048352 17 {
1: x/i $pc 0x8048352 <main+6>: and $0xfffffff0,%esp
(gdb) si
0x08048355 17 {
1: x/i $pc 0x8048355 <main+9>: mov $0x0,%eax
(gdb) si
0x0804835a 17 {
1: x/i $pc 0x804835a <main+14>: sub %eax,%esp
(gdb) si
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)

此時可以使用“i r”命令顯示暫存器中的當前值———“i r”即“Infomation Register”:

(gdb) i r
eax 0xbffff6a4 -1073744220
ecx 0x42015554 1107383636
edx 0x40016bc8 1073834952
ebx 0x42130a14 1108544020
esp 0xbffff6a0 0xbffff6a0
ebp 0xbffff6a8 0xbffff6a8
esi 0x40015360 1073828704
edi 0x80483f0 134513648
eip 0x8048366 0x8048366
eflags 0x386 902
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x33 51

當然也可以顯示任意一個指定的暫存器值:

(gdb) i r eax
eax 0xbffff6a4 -1073744220

最後一個要介紹的命令是“q”,退出(Quit)GDB除錯環境:

(gdb) q
The program is running. exit anyway? (y or n)

cat命令

cat命令連線檔案並列印到標準輸出裝置上,cat經常用來顯示檔案的內容,類似於下的type命令。

注意:當檔案較大時,文字在螢幕上迅速閃過(滾屏),使用者往往看不清所顯示的內容。因此,一般用more等命令分屏顯示。為了控制滾屏,可以按Ctrl+S鍵,停止滾屏;按Ctrl+Q鍵可以恢復滾屏。按Ctrl+C(中斷)鍵可以終止該命令的執行,並且返回Shell提示符狀態。

選項:
-n或-number:有1開始對所有輸出的行數編號;
-b或--number-nonblank:和-n相似,只不過對於空白行不編號;
-s或--squeeze-blank:當遇到有連續兩行以上的空白行,就代換為一行的空白行;
-A:顯示不可列印字元,行尾顯示“$”;
-e:等價於"-vE"選項;
-t:等價於"-vT"選項;

示例:

設ml和m2是當前目錄下的兩個檔案

cat m1 (在螢幕上顯示檔案ml的內容)
cat m1 m2 (同時顯示檔案ml和m2的內容)
cat m1 m2 > file (將檔案ml和m2合併後放入檔案file中

mknod命令

mknod命令用於建立Linux中的字元裝置檔案和塊裝置檔案

引數:

  • 檔名:要建立的裝置檔名;
  • 型別:指定要建立的裝置檔案的型別;
  • 主裝置號:指定裝置檔案的主裝置號;
  • 次裝置號:指定裝置檔案的次裝置號。

示例:

ls -la /dev/ttyUSB*
crw-rw—- 1 root dialout 188, 0 2020-02-13 18:32 /dev/ttyUSB0
mknod /dev/ttyUSB32 c 188 32

擴充:

Linux的裝置管理是和檔案系統緊密結合的,各種裝置都以檔案的形式存放在/dev目錄 下,稱為裝置檔案。應用程式可以開啟、關閉和讀寫這些裝置檔案,完成對裝置的操作,就像操作普通的資料檔案一樣。

為了管理這些裝置,系統為裝置編了號,每 個裝置號又分為主裝置號和次裝置號。主裝置號用來區分不同種類的裝置,而次裝置號用來區分同一型別的多個裝置。對於常用裝置,Linux有約定俗成的編 號,如硬碟的主裝置號是3。

Linux為所有的裝置檔案都提供了統一的操作函式介面,方法是使用資料結構struct file_operations。這個資料結構中包括許多操作函式的指標,如open()、close()、read()和write()等,但由於外設 的種類較多,操作方式各不相同。Struct file_operations結構體中的成員為一系列的介面函式,如用於讀/寫的read/write函式和用於控制的ioctl等。

開啟一個檔案就是呼叫這個檔案file_operations中的open操作。不同型別的檔案有不同的file_operations成員函式,如普通的磁碟資料檔案, 介面函式完成磁碟資料塊讀寫操作;而對於各種裝置檔案,則最終呼叫各自驅動程式中的I/O函式進行具體裝置的操作。這樣,應用程式根本不必考慮操作的是設 備還是普通檔案,可一律當作檔案處理,具有非常清晰統一的I/O介面。所以file_operations是檔案層次的I/O介面。

chmod命令

chmod命令用來變更檔案或目錄的許可權。在UNIX系統家族裡,檔案或目錄許可權的控制分別以讀取、寫入、執行3種一般許可權來區分,另有3種特殊許可權可供運用。使用者可以使用chmod指令去變更檔案與目錄的許可權,設定方式採用文字或數字代號皆可。符號連線的許可權無法變更,如果使用者對符號連線修改許可權,其改變會作用在被連線的原始檔案。

許可權範圍的表示法如下:

u User,即檔案或目錄的擁有者;
g Group,即檔案或目錄的所屬群組;
o Other,除了檔案或目錄擁有者或所屬群組之外,其他使用者皆屬於這個範圍;
a All,即全部的使用者,包含擁有者,所屬群組以及其他使用者;
r 讀取許可權,數字代號為“4”;
w 寫入許可權,數字代號為“2”;
x 執行或切換許可權,數字代號為“1”;
- 不具任何許可權,數字代號為“0”;
s 特殊功能說明:變更檔案或目錄的許可權。

引數:

許可權模式:指定檔案的許可權模式;
檔案:要改變許可權的檔案。

擴充:

Linux用 戶分為:擁有者、組群(Group)、其他(other),Linux系統中,預設的情況下,系統中所有的帳號與一般身份使用者,以及root的相關信 息, 都是記錄在/etc/passwd檔案中。每個人的密碼則是記錄在/etc/shadow檔案下。 此外,所有的組群名稱記錄在/etc/group內!

stat命令

stat命令用於顯示檔案的狀態資訊。stat命令的輸出資訊比ls命令的輸出資訊要更詳細

示例:

[root@localhost ~]# ls -l myfile
-rw-r--r-- 1 root root 0 2020-10-09 myfile

[root@localhost ~]# stat myfile
file: “myfile”
Size: 0               Blocks: 8          IO Block: 4096   一般空檔案
Device: fd00h/64768d    Inode: 194805815   Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-12-12 12:22:35.000000000 +0800
Modify: 2020-10-09 20:44:21.000000000 +0800
Change: 2020-10-09 20:44:21.000000000 +0800

[root@localhost ~]# stat -f myfile
File: "myfile"
id: 0        Namelen: 255     type: ext2/ext3
Block size: 4096       Fundamental block size: 4096
Blocks: Total: 241555461  free: 232910771  Available: 220442547
Inodes: Total: 249364480  Free: 249139691

[root@localhost ~]# stat -t myfile
myfile 0 8 81a4 0 0 fd00 194805815 1 0 0 1292127755 1286628261 1286628261 4096

find命令

find命令用來在指定目錄下查詢檔案。任何位於引數之前的字串都將被視為欲查詢的目錄名。如果使用該命令時,不設定任何引數,則find命令將在當前目錄下查詢子目錄與檔案。並且將查詢到的子目錄和檔案全部進行顯示。

選項:
-amin<分鐘>:查詢在指定時間曾被存取過的檔案或目錄,單位以分鐘計算;
-anewer<參考檔案或目錄>:查詢其存取時間較指定檔案或目錄的存取時間更接近現在的檔案或目錄;
-atime<24小時數>:查詢在指定時間曾被存取過的檔案或目錄,單位以24小時計算;
-cmin<分鐘>:查詢在指定時間之時被更改過的檔案或目錄;
-cnewer<參考檔案或目錄>查詢其更改時間較指定檔案或目錄的更改時間更接近現在的檔案或目錄;
-ctime<24小時數>:查詢在指定時間之時被更改的檔案或目錄,單位以24小時計算;
-daystart:從本日開始計算時間;
-depth:從指定目錄下最深層的子目錄開始查詢;
-empty:尋找檔案大小為0 Byte的檔案,或目錄下沒有任何子目錄或檔案的空目錄;
-exec<執行指令>:假設find指令的回傳值為True,就執行該指令;
-false:將find指令的回傳值皆設為False;
-fls<列表檔案>:此引數的效果和指定“-ls”引數類似,但會把結果儲存為指定的列表檔案;
-follow:排除符號連線;
-fprint<列表檔案>:此引數的效果和指定“-print”引數類似,但會把結果儲存成指定的列表檔案;
-fprint0<列表檔案>:此引數的效果和指定“-print0”引數類似,但會把結果儲存成指定的列表檔案;
-fprintf<列表檔案><輸出格式>:此引數的效果和指定“-printf”引數類似,但會把結果儲存成指定的列表檔案;
-fstype<檔案系統型別>:只尋找該檔案系統型別下的檔案或目錄;
-gid<群組識別碼>:查詢符合指定之群組識別碼的檔案或目錄;
-group<群組名稱>:查詢符合指定之群組名稱的檔案或目錄;
-help或——help:線上幫助;
-ilname<範本樣式>:此引數的效果和指定“-lname”引數類似,但忽略字元大小寫的差別;
-iname<範本樣式>:此引數的效果和指定“-name”引數類似,但忽略字元大小寫的差別;
-inum<inode編號>:查詢符合指定的inode編號的檔案或目錄;
-ipath<範本樣式>:此引數的效果和指定“-path”引數類似,但忽略字元大小寫的差別;
-iregex<範本樣式>:此引數的效果和指定“-regexe”引數類似,但忽略字元大小寫的差別;
-links<連線數目>:查詢符合指定的硬連線數目的檔案或目錄;
-iname<範本樣式>:指定字串作為尋找符號連線的範本樣式;
-ls:假設find指令的回傳值為Ture,就將檔案或目錄名稱列出到標準輸出;
-maxdepth<目錄層級>:設定最大目錄層級;
-mindepth<目錄層級>:設定最小目錄層級;
-mmin<分鐘>:查詢在指定時間曾被更改過的檔案或目錄,單位以分鐘計算;
-mount:此引數的效果和指定“-xdev”相同;
-mtime<24小時數>:查詢在指定時間曾被更改過的檔案或目錄,單位以24小時計算;
-name<範本樣式>:指定字串作為尋找檔案或目錄的範本樣式;
-newer<參考檔案或目錄>:查詢其更改時間較指定檔案或目錄的更改時間更接近現在的檔案或目錄;
-nogroup:找出不屬於本地主機群組識別碼的檔案或目錄;
-noleaf:不去考慮目錄至少需擁有兩個硬連線存在;
-nouser:找出不屬於本地主機使用者識別碼的檔案或目錄;
-ok<執行指令>:此引數的效果和指定“-exec”類似,但在執行指令之前會先詢問使用者,若回答“y”或“Y”,則放棄執行命令;
-path<範本樣式>:指定字串作為尋找目錄的範本樣式;
-perm<許可權數值>:查詢符合指定的許可權數值的檔案或目錄;
-print:假設find指令的回傳值為Ture,就將檔案或目錄名稱列出到標準輸出。格式為每列一個名稱,每個名稱前皆有“./”字串;
-print0:假設find指令的回傳值為Ture,就將檔案或目錄名稱列出到標準輸出。格式為全部的名稱皆在同一行;
-printf<輸出格式>:假設find指令的回傳值為Ture,就將檔案或目錄名稱列出到標準輸出。格式可以自行指定;
-prune:不尋找字串作為尋找檔案或目錄的範本樣式;
-regex<範本樣式>:指定字串作為尋找檔案或目錄的範本樣式;
-size<檔案大小>:查詢符合指定的檔案大小的檔案;
-true:將find指令的回傳值皆設為True;
-type<檔案型別>:只尋找符合指定的檔案型別的檔案;
-uid<使用者識別碼>:查詢符合指定的使用者識別碼的檔案或目錄;
-used<日數>:查詢檔案或目錄被更改之後在指定時間曾被存取過的檔案或目錄,單位以日計算;
-user<擁有者名稱>:查詢符和指定的擁有者名稱的檔案或目錄;
-version或——version:顯示版本資訊;
-xdev:將範圍侷限在先行的檔案系統中;
-xtype<檔案型別>:此引數的效果和指定“-type”引數類似,差別在於它針對符號連線檢查。

示例:

根據檔案或者正規表示式進行匹配

列出當前目錄及子目錄下所有檔案和資料夾

find .

/home目錄下查詢以.txt結尾的檔名

find /home -name "*.txt"

同上,但忽略大小寫

find /home -iname "*.txt"

當前目錄及子目錄下查詢所有以.txt和.pdf結尾的檔案

find . \( -name "*.txt" -o -name "*.pdf" \) 或
find . -name "*.txt" -o -name "*.pdf"

匹配檔案路徑或者檔案

find /usr/ -path "*local*"

基於正規表示式匹配檔案路徑

find . -regex ".*\(\.txt\|\.pdf\)$"

同上,但忽略大小寫

find . -iregex ".*\(\.txt\|\.pdf\)$"

否定引數

找出/home下不是以.txt結尾的檔案

find /home ! -name "*.txt"

根據檔案型別進行搜尋

find . -type 型別引數

型別引數列表:

  • f 普通檔案
  • l 符號連線
  • d 目錄
  • c 字元裝置
  • b 塊裝置
  • s 套接字
  • p Fifo

基於目錄深度搜尋

向下最大深度限制為3

find . -maxdepth 3 -type f

搜尋出深度距離當前目錄至少2個子目錄的所有檔案

find . -mindepth 2 -type f

根據檔案時間戳進行搜尋

find . -type f 時間戳

UNIX/Linux檔案系統每個檔案都有三種時間戳:

  • 訪問時間(-atime/天,-amin/分鐘):使用者最近一次訪問時間。
  • 修改時間(-mtime/天,-mmin/分鐘):檔案最後一次修改時間。
  • 變化時間(-ctime/天,-cmin/分鐘):檔案資料元(例如許可權等)最後一次修改時間。

搜尋最近七天內被訪問過的所有檔案

find . -type f -atime -7

搜尋恰好在七天前被訪問過的所有檔案

find . -type f -atime 7

搜尋超過七天內被訪問過的所有檔案

find . -type f -atime +7

搜尋訪問時間超過10分鐘的所有檔案

find . -type f -amin +10

找出比file.log修改時間更長的所有檔案

find . -type f -newer file.log

根據檔案大小進行匹配

find . -type f -size 檔案大小單元

檔案大小單元:

  • b —— 塊(512位元組)
  • c —— 位元組
  • w —— 字(2位元組)
  • k —— 千位元組
  • M —— 兆位元組
  • G —— 吉位元組

搜尋大於10KB的檔案

find . -type f -size +10k

搜尋小於10KB的檔案

find . -type f -size -10k

搜尋等於10KB的檔案

find . -type f -size 10k

刪除匹配檔案

刪除當前目錄下所有.txt檔案

find . -type f -name "*.txt" -delete

根據檔案許可權/所有權進行匹配

當前目錄下搜尋出許可權為777的檔案

find . -type f -perm 777

找出當前目錄下許可權不是644的php檔案

find . -type f -name "*.php" ! -perm 644

找出當前目錄使用者tom擁有的所有檔案

find . -type f -user tom

找出當前目錄使用者組sunk擁有的所有檔案

find . -type f -group sunk

藉助-exec選項與其他命令結合使用

找出當前目錄下所有root的檔案,並把所有權更改為使用者tom

find .-type f -user root -exec chown tom {} \;

上例中,{} 用於與**-exec**選項結合使用來匹配所有檔案,然後會被替換為相應的檔名。

找出自己家目錄下所有的.txt檔案並刪除

find $HOME/. -name "*.txt" -ok rm {} \;

上例中,-ok和**-exec**行為一樣,不過它會給出提示,是否執行相應的操作。

查詢當前目錄下所有.txt檔案並把他們拼接起來寫入到all.txt檔案中

find . -type f -name "*.txt" -exec cat {} \;> all.txt

將30天前的.log檔案移動到old目錄中

find . -type f -mtime +30 -name "*.log" -exec cp {} old \;

找出當前目錄下所有.txt檔案並以“File:檔名”的形式列印出來

find . -type f -name "*.txt" -exec printf "File: %s\n" {} \;

因為單行命令中-exec引數中無法使用多個命令,以下方法可以實現在-exec之後接受多條命令

-exec ./text.sh {} \;

搜尋但跳出指定的目錄

查詢當前目錄或者子目錄下所有.txt檔案,但是跳過子目錄sk

find . -path "./sk" -prune -o -name "*.txt" -print

find其他技巧收集

要列出所有長度為零的檔案

find . -empty

ps命令

ps命令用於報告當前系統的程式狀態。可以搭配kill指令隨時中斷、刪除不必要的程式。ps命令是最基本同時也是非常強大的程式檢視命令,使用該命令可以確定有哪些程式正在執行和執行的狀態、程式是否結束、程式有沒有僵死、哪些程式佔用了過多的資源等等,總之大部分資訊都是可以通過執行該命令得到的。

常用:

ps -ef
ps -aux

選項:

-a:顯示所有終端機下執行的程式,除了階段作業領導者之外。
a:顯示現行終端機下的所有程式,包括其他使用者的程式。
-A:顯示所有程式。
-c:顯示CLS和PRI欄位。
c:列出程式時,顯示每個程式真正的指令名稱,而不包含路徑,選項或常駐服務的標示。
-C<指令名稱>:指定執行指令的名稱,並列出該指令的程式的狀況。
-d:顯示所有程式,但不包括階段作業領導者的程式。
-e:此選項的效果和指定"A"選項相同。
e:列出程式時,顯示每個程式所使用的環境變數。
-f:顯示UID,PPIP,C與STIME欄位。
f:用ASCII字元顯示樹狀結構,表達程式間的相互關係。
-g<群組名稱>:此選項的效果和指定"-G"選項相同,當亦能使用階段作業領導者的名稱來指定。
g:顯示現行終端機下的所有程式,包括群組領導者的程式。
-G<群組識別碼>:列出屬於該群組的程式的狀況,也可使用群組名稱來指定。
h:不顯示標題列。
-H:顯示樹狀結構,表示程式間的相互關係。
-j或j:採用工作控制的格式顯示程式狀況。
-l或l:採用詳細的格式來顯示程式狀況。
L:列出欄位的相關資訊。
-m或m:顯示所有的執行緒。
n:以數字來表示USER和WCHAN欄位。
-N:顯示所有的程式,除了執行ps指令終端機下的程式之外。
-p<程式識別碼>:指定程式識別碼,並列出該程式的狀況。
p<程式識別碼>:此選項的效果和指定"-p"選項相同,只在列表格式方面稍有差異。
r:只列出現行終端機正在執行中的程式。
-s<階段作業>:指定階段作業的程式識別碼,並列出隸屬該階段作業的程式的狀況。
s:採用程式訊號的格式顯示程式狀況。
S:列出程式時,包括已中斷的子程式資料。
-t<終端機編號>:指定終端機編號,並列出屬於該終端機的程式的狀況。
t<終端機編號>:此選項的效果和指定"-t"選項相同,只在列表格式方面稍有差異。
-T:顯示現行終端機下的所有程式。
-u<使用者識別碼>:此選項的效果和指定"-U"選項相同。
u:以使用者為主的格式來顯示程式狀況。
-U<使用者識別碼>:列出屬於該使用者的程式的狀況,也可使用使用者名稱稱來指定。
U<使用者名稱稱>:列出屬於該使用者的程式的狀況。
v:採用虛擬記憶體的格式顯示程式狀況。
-V或V:顯示版本資訊。
-w或w:採用寬闊的格式來顯示程式狀況。 
x:顯示所有程式,不以終端機來區分。
X:採用舊式的Linux i386登陸格式顯示程式狀況。
-y:配合選項"-l"使用時,不顯示F(flag)欄位,並以RSS欄位取代ADDR欄位 。
-<程式識別碼>:此選項的效果和指定"p"選項相同。
--cols<每列字元數>:設定每列的最大字元數。
--columns<每列字元數>:此選項的效果和指定"--cols"選項相同。
--cumulative:此選項的效果和指定"S"選項相同。
--deselect:此選項的效果和指定"-N"選項相同。
--forest:此選項的效果和指定"f"選項相同。
--headers:重複顯示標題列。
--help:線上幫助。
--info:顯示排錯資訊。
--lines<顯示列數>:設定顯示畫面的列數。
--no-headers:此選項的效果和指定"h"選項相同,只在列表格式方面稍有差異。
--group<群組名稱>:此選項的效果和指定"-G"選項相同。
--Group<群組識別碼>:此選項的效果和指定"-G"選項相同。
--pid<程式識別碼>:此選項的效果和指定"-p"選項相同。
--rows<顯示列數>:此選項的效果和指定"--lines"選項相同。
--sid<階段作業>:此選項的效果和指定"-s"選項相同。
--tty<終端機編號>:此選項的效果和指定"-t"選項相同。
--user<使用者名稱稱>:此選項的效果和指定"-U"選項相同。
--User<使用者識別碼>:此選項的效果和指定"-U"選項相同。
--version:此選項的效果和指定"-V"選項相同。
--widty<每列字元數>:此選項的效果和指定"-cols"選項相同。

top命令

top命令可以實時動態地檢視系統的整體執行情況,是一個綜合了多方資訊監測系統效能和執行資訊的實用工具。通過top命令所提供的互動式介面,用熱鍵可以管理。

選項:

-b:以批處理模式操作;
-c:顯示完整的治命令;
-d:螢幕重新整理間隔時間;
-I:忽略失效過程;
-s:保密模式;
-S:累積模式;
-i<時間>:設定間隔時間;
-u<使用者名稱>:指定使用者名稱;
-p<程式號>:指定程式;
-n<次數>:迴圈顯示的次數。

互動命令:

h:顯示幫助畫面,給出一些簡短的命令總結說明;
k:終止一個程式;
i:忽略閒置和僵死程式,這是一個開關式命令;
q:退出程式;
r:重新安排一個程式的優先順序別;
S:切換到累計模式;
s:改變兩次重新整理之間的延遲時間(單位為s),如果有小數,就換算成ms。輸入0值則系統將不斷重新整理,預設值是5s;
f或者F:從當前顯示中新增或者刪除專案;
o或者O:改變顯示專案的順序;
l:切換顯示平均負載和啟動時間資訊;
m:切換顯示記憶體資訊;
t:切換顯示程式和CPU狀態資訊;
c:切換顯示命令名稱和完整命令列;
M:根據駐留記憶體大小進行排序;
P:根據CPU使用百分比大小進行排序;
T:根據時間/累計時間進行排序;
w:將當前設定寫入~/.toprc檔案中。

示例:

在終端中輸入top

top - 09:44:56 up 16 days, 21:23,  1 user,  load average: 9.59, 4.75, 1.92
Tasks: 145 total,   2 running, 143 sleeping,   0 stopped,   0 zombie
Cpu(s): 99.8%us,  0.1%sy,  0.0%ni,  0.2%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   4147888k total,  2493092k used,  1654796k free,   158188k buffers
Swap:  5144568k total,       56k used,  5144512k free,  2013180k cached

解釋:

top - 09:44:56[當前系統時間],
16 days[系統已經執行了16天],
1 user[個使用者當前登入],
load average: 9.59, 4.75, 1.92[系統負載,即任務佇列的平均長度]
Tasks: 145 total[總程式數],
2 running[正在執行的程式數],
143 sleeping[睡眠的程式數],
0 stopped[停止的程式數],
0 zombie[凍結程式數],
Cpu(s): 99.8%us[使用者空間佔用CPU百分比],
0.1%sy[核心空間佔用CPU百分比],
0.0%ni[使用者程式空間內改變過優先順序的程式佔用CPU百分比],
0.2%id[空閒CPU百分比], 0.0%wa[等待輸入輸出的CPU時間百分比],
0.0%hi[],
0.0%st[],
Mem: 4147888k total[實體記憶體總量],
2493092k used[使用的實體記憶體總量],
1654796k free[空閒記憶體總量],
158188k buffers[用作核心快取的記憶體量]
Swap:  5144568k total[交換區總量],
56k used[使用的交換區總量],
5144512k free[空閒交換區總量],
2013180k cached[緩衝的交換區總量],

umask命令

umask命令用來設定限制新建檔案許可權的掩碼。當新檔案被建立時,其最初的許可權由檔案建立掩碼決定。使用者每次註冊進入系統時,umask命令都被執行, 並自動設定掩碼mode來限制新檔案的許可權。使用者可以通過再次執行umask命令來改變預設值,新的許可權將會把舊的覆蓋掉。

語法:

umask(選項)(引數)

示例:

利用umask命令可以指定哪些許可權將在新檔案的預設許可權中被刪除。例如,可以使用下面的命令建立掩碼,使得組使用者的寫許可權,其他使用者的讀、寫和執行許可權都被取消:

umask u=, g=w, o=rwx

執行該命令以後,對於下面建立的新檔案,其檔案主的許可權未做任何改變,而組使用者沒有寫許可權,其他使用者的所有許可權都被取消。

應注意:操作符“=”在umask命令和chmod命令中的作用恰恰相反。在chmod命令中,利用它來設定指定的許可權,而其餘許可權則被刪除;但是在umask命令中,它將在原有許可權的基礎上刪除指定的許可權。

不能直接利用umask命令建立一個可執行的檔案,使用者只能在其後利用chmod命令使它具有執行許可權。假設執行了命令umask u=, g=w, o=rwx,雖然在命令列中,沒有刪去檔案主和組使用者的執行許可權,但預設的檔案許可權還是640(即 rw-r-----),而不是750(rwxr-x—)。但是,如果建立的是目錄或者通過編譯程式建立的一個可執行檔案,將不受此限制。在這種情況 下,會設定檔案的執行許可權。

也可以使用八進位制數值來設定mode。由於在umask中所指定的許可權是要從檔案中刪除的,所以,如果該檔案原來的初始化許可權是777,那麼執行命令umask 022以後,該檔案的許可權將變為755:如果該檔案原來的初始化許可權是666,那麼該檔案的許可權將變為644。

可以使用下面的命令檢查新建立檔案的預設許可權:

umask -s

選項-s表示以字元形式顯示當前的掩碼。如果直接輸入umask命令,不帶任何引數,那麼將以八進位制形式顯示當前的掩碼。系統預設的掩碼是0022。

date命令

date命令是顯示或設定系統時間與日期。

很多shell指令碼里面需要列印不同格式的時間或日期,以及要根據時間和日期執行操作。延時通常用於指令碼執行過程中提供一段等待的時間。日期可以以多種格式去列印,也可以使用命令設定固定的格式。在類UNIX系統中,日期被儲存為一個整數,其大小為自世界標準時間(UTC)1970年1月1日0時0分0秒起流逝的秒數。

語法:

date(選項)(引數)

選項:

-d<字串>:顯示字串所指的日期與時間。字串前後必須加上雙引號;
-s<字串>:根據字串來設定日期與時間。字串前後必須加上雙引號;
-u:顯示GMT;
--help:線上幫助;
--version:顯示版本資訊。

日期格式:

%H 小時,24小時制(00~23)
%I 小時,12小時制(01~12)
%k 小時,24小時制(0~23)
%l 小時,12小時制(1~12)
%M 分鐘(00~59)
%p 顯示出AM或PM
%r 顯示時間,12小時制(hh:mm:ss %p)
%s 從1970年1月1日00:00:00到目前經歷的秒數
%S 顯示秒(00~59)
%T 顯示時間,24小時制(hh:mm:ss)
%X 顯示時間的格式(%H:%M:%S)
%Z 顯示時區,日期域(CST)
%a 星期的簡稱(Sun~Sat)
%A 星期的全稱(Sunday~Saturday)
%h,%b 月的簡稱(Jan~Dec)
%B 月的全稱(January~December)
%c 日期和時間(Tue Nov 20 14:12:58 2012)
%d 一個月的第幾天(01~31)
%x,%D 日期(mm/dd/yy)
%j 一年的第幾天(001~366)
%m 月份(01~12)
%w 一個星期的第幾天(0代表星期天)
%W 一年的第幾個星期(00~53,星期一為第一天)
%y 年的最後兩個數字(1999則是99)

示例:

格式化輸出:

date +"%Y-%m-%d"
2009-12-07

輸出昨天日期:

date -d "1 day ago" +"%Y-%m-%d"
2012-11-19

2秒後輸出:

date -d "2 second" +"%Y-%m-%d %H:%M.%S"
2012-11-20 14:21.31

傳說中的 1234567890 秒:

date -d "1970-01-01 1234567890 seconds" +"%Y-%m-%d %H:%m:%S"
2009-02-13 23:02:30

普通轉格式:

date -d "2009-12-12" +"%Y/%m/%d %H:%M.%S"
2009/12/12 00:00.00

apache格式轉換:

date -d "Dec 5, 2009 12:00:37 AM" +"%Y-%m-%d %H:%M.%S"
2009-12-05 00:00.37

格式轉換後時間遊走:

date -d "Dec 5, 2009 12:00:37 AM 2 year ago" +"%Y-%m-%d %H:%M.%S"
2007-12-05 00:00.37

加減操作:

date +%Y%m%d                   //顯示前天年月日
date -d "+1 day" +%Y%m%d       //顯示前一天的日期
date -d "-1 day" +%Y%m%d       //顯示後一天的日期
date -d "-1 month" +%Y%m%d     //顯示上一月的日期
date -d "+1 month" +%Y%m%d     //顯示下一月的日期
date -d "-1 year" +%Y%m%d      //顯示前一年的日期
date -d "+1 year" +%Y%m%d      //顯示下一年的日期

設定時間:

date -s                        //設定當前時間,只有root許可權才能設定,其他只能檢視
date -s 20120523               //設定成20120523,這樣會把具體時間設定成空00:00:00
date -s 01:01:01               //設定具體時間,不會對日期做更改
date -s "01:01:01 2012-05-23"  //這樣可以設定全部時間
date -s "01:01:01 20120523"    //這樣可以設定全部時間
date -s "2012-05-23 01:01:01"  //這樣可以設定全部時間
date -s "20120523 01:01:01"    //這樣可以設定全部時間

有時需要檢查一組命令花費的時間,舉例:

#!/bin/bash

start=$(date +%s)
nmap man.linuxde.net &> /dev/null

end=$(date +%s)
difference=$(( end - start ))
echo $difference seconds.

其他命令

#重啟
reboot 
#退出 和CTRL+D是一樣的效果
#這裡的退出指的是退出當前所在shell(可以暫理解為工作環境)
exit 
#更新源
#這裡是Ubuntu的更新  如果是CentOS系統的話使用yum update
#yum和apt操作沒多少區別
apt update -y 
#安裝vim
#也可以使用apt-get 一般使用apt就行了 -y引數是為了直接安裝而不用輸入中間的詢問
apt install vim -y
#檢視ip 
#當你遠端連線伺服器或者虛擬機器的時候其實是使用IP+埠連線的
#如果要連線遠端伺服器的話確保三點 1:IP 2:SSH服務是否開啟 3:埠是否開啟
#SSH服務是要下載安裝的  安裝同上 apt install ssh -y
ip addr 
#停止防火牆
service ufw stop 
#檢視開放埠
netstat -anptl 
#檢視歷史命令倒數20
history | tail -20 

快捷鍵

剪下板操作

#注意 如果用xshell有些快捷鍵可能會衝突 比如下面這兩個快捷鍵在xshell中是不能執行的

Ctrl+Shift+C 複製
Ctrl+Shift+V 貼上

游標操作

Ctrl+A(ahead) 開始位置
Ctrl+E(end) 最後位置
Ctrl+LeftArrow 游標移動到上一個單詞的詞首
Ctrl+RightArrow 游標移動到下一個單詞的詞尾
Ctrl+F(forwards) 游標向後移動一個字元,相當與→
Ctrl+B(backwards) 游標向前移動一個字元,相當與←
Alt+F 游標向後移動一個單詞
Alt+B 游標向前移動一個單詞
Esc+B 移動到當前單詞的開頭
Esc+F 移動到當前單詞的結尾

文字處理操作

Ctrl+U 剪下游標至行首的內容
Ctrl+K 剪下游標至行尾的內容
Ctrl+W 剪下游標到詞首的內容
Alt+D 剪下游標到詞尾的內容
Ctrl+D 刪除游標所在字元 相當於Delete
Ctrl+H 刪除游標前的字元 相當於Backspace
Ctrl+Y 貼上剛才所刪除的字元
Ctrl+7 恢復剛剛的內容
Ctrl+(X U) 撤銷剛才的操作
Esc+T 顛倒游標相鄰單詞的位置
Alt+T 顛倒游標相鄰單詞的位置
Ctrl+T 顛倒游標相鄰字元的位置
Alt+C 將游標所在字元到詞尾改為首字母大寫
Alt+U 將游標所在字元到詞尾轉化為大寫
Alt+L 將游標所在字元到詞尾轉化為小寫
Ctrl+V 插入特殊字元,如Ctrl+(V Tab)加入Tab字元鍵

任務處理操作

Ctrl+C 刪除整行/終止
Ctrl+L 重新整理螢幕
Ctrl+S 掛起當前shell
Ctrl+Q 重新啟用掛起的shell

標籤頁處理操作

Shift+Ctrl+T 新建標籤頁
Shift+Ctrl+W 關閉標籤頁
Ctrl+PageUp 前一標籤頁
Ctrl+PageDown 後一標籤頁
Shift+Ctrl+PageUp 標籤頁左移
Shift+Ctrl+PageDown 標籤頁右移
Alt+1,2,3… 切換到標籤頁1,2,3…

視窗操作

Shift+Ctrl+N 新建視窗
Shift+Ctrl+Q 關閉終端
F11 全屏
Ctrl+Plus 放大
Ctrl+Minus 減小
Ctrl+0 原始大小
Shift+UpArrow 向上滾屏
Shift+DownArrow 向下滾屏
Shift+PageUp 向上翻頁
Shift+PageDown 向下翻頁

歷史命令操作

↑(Ctrl+P(previous)) 顯示上一條命令
↑(Ctrl+N(next)) 顯示下一條命令
!Num 執行命令歷史列表的第Num條命令
!! 執行上一條命令
!?String? 執行含有String字串的最新命令
Alt+Shift+, 歷史列表第一項
Alt+Shift+. 歷史列表最後一項
Ctrl+R(retrieve) String 搜尋包含String字串的命令/繼續向上檢索(Ctrl+S 向下檢索)
!$ 以上一條命令的引數做為其引數

其他操作

Ctrl+M 相當於Enter
Ctrl+O 相當於Enter
Ctrl+[ 相當於Esc
Esc Esc Esc 顯示所有支援的命令
Tab Tab 顯示所有支援的命令
Ctrl+(I I) 顯示所有支援的命令
Ctrl+X Shift+2顯示可能hostname補全
Ctrl+(X X) 在EOL和當前游標位置移動

Windows常用命令(快捷鍵)

快捷鍵

關閉網頁 CTRL+W

關閉其它頁面 ALT+F4

還原網頁 CTRL+SHIFT+T

快速搜尋 CTRL+C,CTRL+F,CTRL+V

切屏 ALT+TAB

CTRL  WINDOWS +D 開一個新桌面

CTRL WINDOWS +F4  刪除新開桌面

CTRL WINDOWS  +左右 翻桌面

systeminfo 檢視系統資訊

常用命令

使用WIN+R調出命令列輸入cmd 點選ok
在這裡插入圖片描述

檢視IP

ipconfig 檢視網路卡ip 可以使用管道|more

在這裡插入圖片描述

測試是否聯網

ping www.baidu.com #下面這樣則證明連線通暢

在這裡插入圖片描述

檢視目錄下面內容

dir #類似於linux下的ls

在這裡插入圖片描述

檢視目錄結構

tree | more #和linux下的tree類似

在這裡插入圖片描述

進入資料夾

cd #和linux下的cd類似

在這裡插入圖片描述

關機

shutdown /s /t 0 #立刻關機

在這裡插入圖片描述

Vi/Vim操作使用

vim鍵盤圖
在這裡插入圖片描述

基本上 vi/vim 共分為三種模式,分別是命令模式(Command mode)輸入模式(Insert mode)底線命令模式(Last line mode)。 這三種模式的作用分別是:

命令模式

使用者剛剛啟動 vi/vim,便進入了命令模式。

此狀態下敲擊鍵盤動作會被Vim識別為命令,而非輸入字元。比如我們此時按下i,並不會輸入一個字元,i被當作了一個命令。

以下是常用的幾個命令:

  • i 切換到輸入模式,以輸入字元。
  • x 刪除當前游標所在處的字元。
  • : 切換到底線命令模式,以在最底一行輸入命令。

若想要編輯文字:啟動Vim,進入了命令模式,按下i,切換到輸入模式。

命令模式只有一些最基本的命令,因此仍要依靠底線命令模式輸入更多命令。

輸入模式

在命令模式下按下i就進入了輸入模式。

在輸入模式中,可以使用以下按鍵:

  • 字元按鍵以及Shift組合,輸入字元
  • ENTER,Enter鍵,換行
  • BACK SPACE,退格鍵,刪除游標前一個字元
  • DEL,刪除鍵,刪除游標後一個字元
  • 方向鍵,在文字中移動游標
  • HOME/END,移動游標到行首/行尾
  • Page Up/Page Down,上/下翻頁
  • Insert,切換游標為輸入/替換模式,游標將變成豎線/下劃線
  • ESC,退出輸入模式,切換到命令模式

底線命令模式

在命令模式下按下:(英文冒號)就進入了底線命令模式。

底線命令模式可以輸入單個或多個字元的命令,可用的命令非常多。

在底線命令模式中,基本的命令有(已經省略了冒號):

  • q 退出程式
  • w 儲存檔案

按ESC鍵可隨時退出底線命令模式。

vi/vim 按鍵說明

除了上面簡易範例的 i, Esc, :wq 之外,其實 vim 還有非常多的按鍵可以使用。

第一部分:一般模式可用的游標移動、複製貼上、搜尋替換等

移動游標的方法
h 或 向左箭頭鍵(←)游標向左移動一個字元
j 或 向下箭頭鍵(↓)游標向下移動一個字元
k 或 向上箭頭鍵(↑)游標向上移動一個字元
l 或 向右箭頭鍵(→)游標向右移動一個字元
如果你將右手放在鍵盤上的話,你會發現 hjkl 是排列在一起的,因此可以使用這四個按鈕來移動游標。 如果想要進行多次移動的話,例如向下移動 30 行,可以使用 “30j” 或 “30↓” 的組合按鍵, 亦即加上想要進行的次數(數字)後,按下動作即可!
[Ctrl] + [f]螢幕『向下』移動一頁,相當於 [Page Down]按鍵 (常用)
[Ctrl] + [b]螢幕『向上』移動一頁,相當於 [Page Up] 按鍵 (常用)
[Ctrl] + [d]螢幕『向下』移動半頁
[Ctrl] + [u]螢幕『向上』移動半頁
+游標移動到非空格符的下一行
-游標移動到非空格符的上一行
n那個 n 表示『數字』,例如 20 。按下數字後再按空格鍵,游標會向右移動這一行的 n 個字元。例如 20 則游標會向後面移動 20 個字元距離。
0 或功能鍵[Home]這是數字『 0 』:移動到這一行的最前面字元處 (常用)
$ 或功能鍵[End]移動到這一行的最後面字元處(常用)
H游標移動到這個螢幕的最上方那一行的第一個字元
M游標移動到這個螢幕的中央那一行的第一個字元
L游標移動到這個螢幕的最下方那一行的第一個字元
G移動到這個檔案的最後一行(常用)
nGn 為數字。移動到這個檔案的第 n 行。例如 20G 則會移動到這個檔案的第 20 行(可配合 :set nu)
gg移動到這個檔案的第一行,相當於 1G 啊! (常用)
nn 為數字。游標向下移動 n 行(常用)
搜尋替換
/word向游標之下尋找一個名稱為 word 的字串。例如要在檔案內搜尋 vbird 這個字串,就輸入 /vbird 即可! (常用)
?word向游標之上尋找一個字串名稱為 word 的字串。
n這個 n 是英文按鍵。代表重複前一個搜尋的動作。舉例來說, 如果剛剛我們執行 /vbird 去向下搜尋 vbird 這個字串,則按下 n 後,會向下繼續搜尋下一個名稱為 vbird 的字串。如果是執行 ?vbird 的話,那麼按下 n 則會向上繼續搜尋名稱為 vbird 的字串!
N這個 N 是英文按鍵。與 n 剛好相反,為『反向』進行前一個搜尋動作。 例如 /vbird 後,按下 N 則表示『向上』搜尋 vbird 。
使用 /word 配合 n 及 N 是非常有幫助的!可以讓你重複的找到一些你搜尋的關鍵詞!
:n1,n2s/word1/word2/gn1 與 n2 為數字。在第 n1 與 n2 行之間尋找 word1 這個字串,並將該字串取代為 word2 !舉例來說,在 100 到 200 行之間搜尋 vbird 並取代為 VBIRD 則: 『:100,200s/vbird/VBIRD/g』。(常用)
:1,$s/word1/word2/g:%s/word1/word2/g從第一行到最後一行尋找 word1 字串,並將該字串取代為 word2 !(常用)
:1,$s/word1/word2/gc:%s/word1/word2/gc從第一行到最後一行尋找 word1 字串,並將該字串取代為 word2 !且在取代前顯示提示字元給使用者確認 (confirm) 是否需要取代!(常用)
刪除、複製與貼上
x, X在一行字當中,x 為向後刪除一個字元 (相當於 [del] 按鍵), X 為向前刪除一個字元(相當於 [backspace] 亦即是退格鍵) (常用)
nxn 為數字,連續向後刪除 n 個字元。舉例來說,我要連續刪除 10 個字元, 『10x』。
dd刪除遊標所在的那一整行(常用)
nddn 為數字。刪除游標所在的向下 n 行,例如 20dd 則是刪除 20 行 (常用)
d1G刪除游標所在到第一行的所有資料
dG刪除游標所在到最後一行的所有資料
d$刪除遊標所在處,到該行的最後一個字元
d0那個是數字的 0 ,刪除遊標所在處,到該行的最前面一個字元
yy複製遊標所在的那一行(常用)
nyyn 為數字。複製游標所在的向下 n 行,例如 20yy 則是複製 20 行(常用)
y1G複製遊標所在行到第一行的所有資料
yG複製遊標所在行到最後一行的所有資料
y0複製游標所在的那個字元到該行行首的所有資料
y$複製游標所在的那個字元到該行行尾的所有資料
p, Pp 為將已複製的資料在游標下一行貼上,P 則為貼在遊標上一行! 舉例來說,我目前游標在第 20 行,且已經複製了 10 行資料。則按下 p 後, 那 10 行資料會貼在原本的 20 行之後,亦即由 21 行開始貼。但如果是按下 P 呢? 那麼原本的第 20 行會被推到變成 30 行。 (常用)
J將游標所在行與下一行的資料結合成同一行
c重複刪除多個資料,例如向下刪除 10 行,[ 10cj ]
u復原前一個動作。(常用)
[Ctrl]+r重做上一個動作。(常用)
這個 u 與 [Ctrl]+r 是很常用的指令!一個是復原,另一個則是重做一次~ 利用這兩個功能按鍵,你的編輯,嘿嘿!很快樂的啦!
.不要懷疑!這就是小數點!意思是重複前一個動作的意思。 如果你想要重複刪除、重複貼上等等動作,按下小數點『.』就好了! (常用)

第二部分:一般模式切換到編輯模式的可用的按鈕說明

進入輸入或取代的編輯模式
i, I進入輸入模式(Insert mode): i 為『從目前游標所在處輸入』, I 為『在目前所在行的第一個非空格符處開始輸入』。 (常用)
a, A進入輸入模式(Insert mode): a 為『從目前游標所在的下一個字元處開始輸入』, A 為『從游標所在行的最後一個字元處開始輸入』。(常用)
o, O進入輸入模式(Insert mode): 這是英文字母 o 的大小寫。o 為『在目前游標所在的下一行處輸入新的一行』; O 為在目前游標所在處的上一行輸入新的一行!(常用)
r, R進入取代模式(Replace mode): r 只會取代游標所在的那一個字元一次;R會一直取代游標所在的文字,直到按下 ESC 為止;(常用)
上面這些按鍵中,在 vi 畫面的左下角處會出現『–INSERT–』或『–REPLACE–』的字樣。 由名稱就知道該動作了吧!!特別注意的是,我們上面也提過了,你想要在檔案裡面輸入字元時, 一定要在左下角處看到 INSERT 或 REPLACE 才能輸入喔!
[Esc]退出編輯模式,回到一般模式中(常用)

第三部分:一般模式切換到指令行模式的可用的按鈕說明

指令行的儲存、離開等指令
:w將編輯的資料寫入硬碟檔案中(常用)
:w!若檔案屬性為『只讀』時,強制寫入該檔案。不過,到底能不能寫入, 還是跟你對該檔案的檔案許可權有關啊!
:q離開 vi (常用)
:q!若曾修改過檔案,又不想儲存,使用 ! 為強制離開不儲存檔案。
注意一下啊,那個驚歎號 (!) 在 vi 當中,常常具有『強制』的意思~
:wq儲存後離開,若為 :wq! 則為強制儲存後離開 (常用)
ZZ這是大寫的 Z 喔!若檔案沒有更動,則不儲存離開,若檔案已經被更動過,則儲存後離開!
:w [filename]將編輯的資料儲存成另一個檔案(類似另存新檔)
:r [filename]在編輯的資料中,讀入另一個檔案的資料。亦即將 『filename』 這個檔案內容加到遊標所在行後面
:n1,n2 w [filename]將 n1 到 n2 的內容儲存成 filename 這個檔案。
:! command暫時離開 vi 到指令行模式下執行 command 的顯示結果!例如 『:! ls /home』即可在 vi 當中察看 /home 底下以 ls 輸出的檔案資訊!
vim 環境的變更
:set nu顯示行號,設定之後,會在每一行的字首顯示該行的行號
:set nonu與 set nu 相反,為取消行號!

特別注意,在 vi/vim 中,數字是很有意義的!數字通常代表重複做幾次的意思! 也有可能是代表去到第幾個什麼什麼的意思。

舉例來說,要刪除 50 行,則是用 『50dd』 數字加在動作之前,如我要向下移動 20 行呢?那就是『20j』或者是『20↓』即可。

管道

符號表示

| 和管道特別形象。

作用

管道是Linux中很重要的一種通訊方式,是把一個程式的輸出直接連線到另一個程式的輸入,常說的管道多是指無名管道,無名管道只能用於具有親緣關係的程式之間,這是它與有名管道的最大區別。
有名管道叫named pipe或者FIFO(先進先出),可以用函式mkfifo()建立。

實現機制

在Linux中,管道是一種使用非常頻繁的通訊機制。從本質上說,管道也是一種檔案,但它又和一般的檔案有所不同,管道可以克服使用檔案進行通訊的兩個問題,具體表現為:

  1. 限制管道的大小。實際上,管道是一個固定大小的緩衝區。在Linux中,該緩衝區的大小為1頁,即4K位元組,使得它的大小不象檔案那樣不加檢驗地增長。使用單個固定緩衝區也會帶來問題,比如在寫管道時可能變滿,當這種情況發生時,隨後對管道的write()呼叫將預設地被阻塞,等待某些資料被讀取,以便騰出足夠的空間供write()呼叫寫。
  2. 讀取程式也可能工作得比寫程式快。當所有當前程式資料已被讀取時,管道變空。當這種情況發生時,一個隨後的read()呼叫將預設地被阻塞,等待某些資料被寫入,這解決了read()呼叫返回檔案結束的問題。

注意:從管道讀資料是一次性操作,資料一旦被讀,它就從管道中被拋棄,釋放空間以便寫更多的資料。

管道的結構

在 Linux 中,管道的實現並沒有使用專門的資料結構,而是藉助了檔案系統的file結構和VFS的索引節點inode。通過將兩個 file 結構指向同一個臨時的 VFS 索引節點,而這個 VFS 索引節點又指向一個物理頁面而實現的。

管道的讀寫

管道實現的原始碼在fs/pipe.c中,在pipe.c中有很多函式,其中有兩個函式比較重要,即管道讀函式pipe_read()和管道寫函式pipe_wrtie()。管道寫函式通過將位元組複製到 VFS 索引節點指向的實體記憶體而寫入資料,而管道讀函式則通過複製實體記憶體中的位元組而讀出資料。當然,核心必須利用一定的機制同步對管道的訪問,為此,核心使用了鎖、等待佇列和訊號。
當寫程式向管道中寫入時,它利用標準的庫函式write(),系統根據庫函式傳遞的檔案描述符,可找到該檔案的 file 結構。file 結構中指定了用來進行寫操作的函式(即寫入函式)地址,於是,核心呼叫該函式完成寫操作。寫入函式在向記憶體中寫入資料之前,必須首先檢查 VFS 索引節點中的資訊,同時滿足如下條件時,才能進行實際的記憶體複製工作:
1.記憶體中有足夠的空間可容納所有要寫入的資料;
2.記憶體沒有被讀程式鎖定。
如果同時滿足上述條件,寫入函式首先鎖定記憶體,然後從寫程式的地址空間中複製資料到記憶體。否則,寫入程式就休眠在 VFS 索 引節點的等待佇列中,接下來,核心將呼叫排程程式,而排程程式會選擇其他程式執行。寫入程式實際處於可中斷的等待狀態,當記憶體中有足夠的空間可以容納寫入 資料,或記憶體被解鎖時,讀取程式會喚醒寫入程式,這時,寫入程式將接收到訊號。當資料寫入記憶體之後,記憶體被解鎖,而所有休眠在索引節點的讀取程式會被喚醒。
管道的讀取過程和寫入過程類似。但是,程式可以在沒有資料或記憶體被鎖定時立即返回錯誤資訊,而不是阻塞該程式,這依賴於檔案或管道的開啟模式。反之,程式可 以休眠在索引節點的等待佇列中等待寫入程式寫入資料。當所有的程式完成了管道操作之後,管道的索引節點被丟棄,而共享資料頁也被釋放。
因為管道的實現涉及很多檔案的操作,因此,當讀者學完有關檔案系統的內容後來讀pipe.c中的程式碼,你會覺得並不難理解。
Linux 管道對阻塞之前一次寫操作的大小有限制。 專門為每個管道所使用的核心級緩衝區確切為 4096 位元組。 除非閱讀器清空管道,否則一次超過 4K 的寫操作將被阻塞。 實際上這算不上什麼限制,因為讀和寫操作是在不同的執行緒中實現的。

Linux中管道的使用

管道是一種通訊機制,通常用於程式間的通訊(也可通過socket進行網路通訊),它表現出來的形式將前面每一個程式的輸出(stdout)直接作為下一個程式的輸入(stdin)。

管道命令使用|作為界定符號

  • 管道命令僅能處理standard output,對於standard error output會予以忽略。
    less,more,head,tail...都是可以接受standard input的命令,所以他們是管道命令
    ls,cp,mv並不會接受standard input的命令,所以他們就不是管道命令了。
  • 管道命令必須要能夠接受來自前一個命令的資料成為standard input繼續處理才行。

示例:

$ ls -al /etc | less

通過管道將ls -al的輸出作為 下一個命令less的輸入,方便瀏覽。
在這裡插入圖片描述

重定向

我們知道,Linux 中標準的輸入裝置預設指的是鍵盤,標準的輸出裝置預設指的是顯示器。

  • 輸入重定向:指的是重新指定裝置來代替鍵盤作為新的輸入裝置;
  • 輸出重定向:指的是重新指定裝置來代替顯示器作為新的輸出裝置。

通常是用檔案或命令的執行結果來代替鍵盤作為新的輸入裝置,而新的輸出裝置通常指的就是檔案。

Linux輸入重定向

對於輸入重定向來說,其需要用到的符號以及作用如表 1 所示。

命令符號格式作用
命令 < 檔案將指定檔案作為命令的輸入裝置
命令 << 分界符表示從標準輸入裝置(鍵盤)中讀入,直到遇到分界符才停止(讀入的資料不包括分界符),這裡的分界符其實就是自定義的字串
命令 < 檔案 1 > 檔案 2將檔案 1 作為命令的輸入裝置,該命令的執行結果輸出到檔案 2 中。

【例 1】
預設情況下,cat 命令會接受標準輸入裝置(鍵盤)的輸入,並顯示到控制檯,但如果用檔案代替鍵盤作為輸入裝置,那麼該命令會以指定的檔案作為輸入裝置,並將檔案中的內容讀取並顯示到控制檯。

以 /etc/passwd 檔案(儲存了系統中所有使用者的基本資訊)為例,執行如下命令:

[root@localhost ~]# cat /etc/passwd
#這裡省略輸出資訊,讀者可自行檢視
[root@localhost ~]# cat < /etc/passwd
#輸出結果同上面命令相同

注意,雖然執行結果相同,但第一行代表是以鍵盤作為輸入裝置,而第二行程式碼是以 /etc/passwd 檔案作為輸入裝置。

【例 2】

[root@localhost ~]# cat << 0
>c.biancheng.net
>Linux
>0
c.biancheng.net
Linux

可以看到,當指定了 0 作為分界符之後,只要不輸入 0,就可以一直輸入資料。

【例 3】
首先,新建文字檔案 a.tx,然後執行如下命令:

[root@localhost ~]# cat a.txt
[root@localhost ~]# cat < /etc/passwd > a.txt
[root@localhost ~]# cat a.txt
#輸出了和 /etc/passwd 檔案內容相同的資料

可以看到,通過重定向 /etc/passwd 作為輸入裝置,並輸出重定向到 a.txt,最終實現了將 /etc/passwd 檔案中內容複製到 a.txt 中。

Linux輸出重定向

相較於輸入重定向,我們使用輸出重定向的頻率更高。並且,和輸入重定向不同的是,輸出重定向還可以細分為標準輸出重定向和錯誤輸出重定向兩種技術。

例如,使用 ls 命令分別檢視兩個檔案的屬性資訊,但其中一個檔案是不存在的,如下所示:

[root@localhost ~]# touch demo1.txt
[root@localhost ~]# ls -l demo1.txt
-rw-rw-r--. 1 root root 0 Oct 12 15:02 demo1.txt
[root@localhost ~]# ls -l demo2.txt  <-- 不存在的檔案
ls: cannot access demo2.txt: No such file or directory

上述命令中,demo1.txt 是存在的,因此正確輸出了該檔案的一些屬性資訊,這也是該命令執行的標準輸出資訊;而 demo2.txt 是不存在的,因此執行 ls 命令之後顯示的報錯資訊,是該命令的錯誤輸出資訊。

再次強調,要想把原本輸出到螢幕上的資料轉而寫入到檔案中,這兩種輸出資訊就要區別對待。

在此基礎上,標準輸出重定向和錯誤輸出重定向又分別包含清空寫入和追加寫入兩種模式。因此,對於輸出重定向來說,其需要用到的符號以及作用如表 2 所示。

命令符號格式作用
命令 > 檔案將命令執行的標準輸出結果重定向輸出到指定的檔案中,如果該檔案已包含資料,會清空原有資料,再寫入新資料。
命令 2> 檔案將命令執行的錯誤輸出結果重定向到指定的檔案中,如果該檔案中已包含資料,會清空原有資料,再寫入新資料。
命令 >> 檔案將命令執行的標準輸出結果重定向輸出到指定的檔案中,如果該檔案已包含資料,新資料將寫入到原有內容的後面。
命令 2>> 檔案將命令執行的錯誤輸出結果重定向到指定的檔案中,如果該檔案中已包含資料,新資料將寫入到原有內容的後面。
命令 >> 檔案 2>&1 或者 命令 &>> 檔案將標準輸出或者錯誤輸出寫入到指定檔案,如果該檔案中已包含資料,新資料將寫入到原有內容的後面。注意,第一種格式中,最後的 2>&1 是一體的,可以認為是固定寫法。

【例 4】新建一個包含有 “Linux” 字串的文字檔案 Linux.txt,以及空文字檔案 demo.txt,然後執行如下命令:

[root@localhost ~]# cat Linux.txt > demo.txt
[root@localhost ~]# cat demo.txt
Linux
[root@localhost ~]# cat Linux.txt > demo.txt
[root@localhost ~]# cat demo.txt
Linux   <--這裡的 Linux 是清空原有的 Linux 之後,寫入的新的 Linux
[root@localhost ~]# cat Linux.txt >> demo.txt
[root@localhost ~]# cat demo.txt
Linux
Linux   <--以追加的方式,新資料寫入到原有資料之後
[root@localhost ~]# cat b.txt > demo.txt
cat: b.txt: No such file or directory <-- 錯誤輸出資訊依然輸出到了顯示器中
[root@localhost ~]# cat b.txt 2> demo.txt
[root@localhost ~]# cat demo.txt
cat: b.txt: No such file or directory <--清空檔案,再將錯誤輸出資訊寫入到該檔案中
[root@localhost ~]# cat b.txt 2>> demo.txt
[root@localhost ~]# cat demo.txt
cat: b.txt: No such file or directory
cat: b.txt: No such file or directory <--追加寫入錯誤輸出資訊

其他

分屏:豎屏- vim -O 橫屏- vim -o 切換- ctrl +w w sp vs

!$ #找上一步最後一個引數
!! #上一個命令
echo $? #上一條命令的返回值 0-正確 非0-錯誤
!#加history裡行號直接執行該命令
stat #檢視建立檔案、修改檔案的時間
cp #複製檔案
mv #移動檔案位置、改名
ls -a #檢視隱藏檔案
find / -name='sang'#查詢sang
chmod #設定檔案許可權
sudo #更改使用者
cat #連線檔案並列印到標準輸出裝置
> #輸入定向 (覆蓋)
>> #輸入定向(追加)
<< #輸出定向
cat >> xx<<EOF(可自定義)#列印XX內容到螢幕 可輸入內容到XX 輸EOF則退出該命令
cat /dev/null> /etc/test.txt #/etc/test.txt文件內容
| #管道-檔案輸入通道
mknod name p #建立管道
fg
bg
kill %1
su #切換使用者,只切換了root身份,還是普通使用者的shell
su - #連使用者和shell環境一起切換成root使用者了
rename #重新命名
VIM操作
dw #刪一個單詞
shift v#行模式
ctrl v# 列模式
ps -ef 
ps
pstree

C/C++嵌入式程式碼編寫部分

檔案操作

檔案描述符是什麼

檔案描述符是一個非負的索引值(一般從3開始,0、1、2已經被使用),指向核心中的 “檔案記錄表”,核心為程式中要開啟的檔案維護者一個“檔案記錄表;

當開啟一個現存檔案或建立一個新檔案時,核心就向程式返回一個檔案描述符(核心記錄表某一欄的索引);
當需要讀寫檔案時,也需要把檔案描述符作為引數傳遞給相應的函式。
Linux 下所有對裝置和檔案的操作都使用檔案描述符來進行。

常見的檔案描述符型別

一個程式啟動時,會預設開啟三個檔案–標準輸入、標準輸出和標準出錯處理。

  • 0:表示標準輸入,對應巨集為:STDIN_FILENO,函式 scanf() 使用的是標準輸入;

  • 1:表示標準輸出,對應巨集為:STDOUT_FILENO, 函式 printf() 使用的是標準輸出;

  • 2:表示標準出錯處理,對應的巨集為:STDERR_NO;

你也可以使用函式 fscanf() 和 fprintf() 使用不同的 檔案描述符 重定向程式的 I/O 到不同的檔案。

使用檔案描述符的函式

若要訪問檔案,而且呼叫的函式又是 write、read、open和close時,就必須用到檔案描述符(一般從3開始)。
若呼叫的函式為 fwrite、fread、fopen和fclose時,就可以繞過直接控制檔案描述符,使用的則是與檔案描述符對應的檔案流。

檔案描述符的建立

程式獲取檔案描述符最常見的方法就是通過系統函式open或create獲取,或者是從父程式繼承。
從父程式繼承的話,子程式就可以訪問父程式所使用的檔案。我們再深入想想,程式是獨立執行的,互不干擾,如果父子程式要通訊的話,是不是就可以通過這些都能訪問的檔案入手。
檔案描述符對於每一個程式是唯一的,每個程式都有一張檔案描述符表,用於管理檔案描述符。當使用fork建立子程式的話,子程式會獲得父程式所有檔案描述符的副本,這些檔案描述符在執行fork時開啟。在由fcntl、dup和dup2子例程複製或拷貝某個程式時,會發生同樣的複製過程。

fork對檔案描述符的影響

fork會導致子程式繼承父程式開啟的檔案描述符,其本質是將父程式的整個檔案描述符表複製一份,放到子程式的PCB中。因此父、子程式中相同檔案描述符(檔案描述符為整數)指向的是同一個檔案表元素,這將導致父(子)程式讀取檔案後,子(父)程式將讀取同一檔案的後續內容。

int main(void)  
{  
       int fd, pid, status;  
       char buf[10];  
         if ((fd = open("./test.txt", O_RDONLY)) < 0) {  
                 perror("open");  exit(-1);  
        }  
         if ((pid = fork()) < 0) {  
               perror("fork");  exit(-1);  
        } else if (pid == 0) {  //child  
                read(fd, buf, 2);  
                write(STDOUT_FILENO, buf, 2);  
        } else {  //parent  
                 sleep(2);  
                lseek(fd, SEEK_CUR, 1);  
                read(fd, buf, 3);  
                 write(STDOUT_FILENO, buf, 3);  
                 write(STDOUT_FILENO, "\n", 1);  
        }  
         return 0;  
 }  

假設,./test.txt的內容是abcdefg。那麼子程式的18行將讀到字元ab;由於,父、子程式的檔案描述符fd都指向同一個檔案表元素,因此當父程式執行23行時,fd對應的檔案的讀寫指標將移動到字元d,而不是字元b,從而24行讀到的是字元def,而不是字元bcd。程式執行的最終結果是列印abdef,而不是abbcd

在這裡插入圖片描述
在這裡插入圖片描述

對於 Linux 而言,所有對裝置和檔案的操作都使用檔案描述符來進行的。檔案描述符是一個非負的整數,它是一個索引值,並指向核心中每個程式開啟檔案的記錄表。當開啟一個現存檔案或建立一個新檔案時,核心就向程式返回一個檔案描述符;當需要讀寫檔案時, 也需要把檔案描述符作為引數傳遞給相應的函式。

通常,一個程式啟動時,都會開啟 3 個檔案:標準輸入、標準輸出和標準出錯處理。這 3 個檔案分別對應檔案描述符為 0、1 和 2(也就是巨集替換 STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,鼓勵讀者使用這些巨集替換)

open 函式

作用:

開啟或建立檔案
可指定檔案屬性及使用者許可權等引數

函式原型

int  open(const  char *pathname,  flags,  int perms)
所需標頭檔案#include <sys/types.h>,<sys/stat.h>,<fcntl.h>
函式原型int open(const char *pathname, flags, int perms)
函式傳入值pathname檔名稱
flag:檔案開啟方式O_RDONLY:O_WRONLY:O_RDWR
O_CREAT|O_EXCL|O_TRUNC|O_APPEND
permsS_I(R/W/X/USER/GRP/OTH), 8進位制存取許可權
函式返回值成功:返回檔案描述符(0,1,2,3,4……)
失敗:-1

在這裡插入圖片描述

close 函式

關閉一個已經開啟的檔案描述符
程式結束,它所有已開啟的檔案描述符都由核心自動關閉
在這裡插入圖片描述

open/close函式程式碼示例

/*open.c*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
    int fd;
    /*呼叫 open 函式,以可讀寫的方式開啟,注意選項可以用“|”符號連線*/
    if ((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0)
    {
        perror("open:");
        exit(1);
    }
    else
    {
        printf("Open file: hello.c %d\n", fd);
    }
    if (close(fd) < 0)
    {
        perror("close:");
        exit(1);
    }
    else
        printf("Close hello.c\n");
    exit(0);
}

read 函式

從檔案描述符中讀資料
從終端裝置檔案讀資料時,通常一次最多讀一行
在這裡插入圖片描述

write 函式

向檔案描述符中寫資料,從當前寫指標處開始
若磁碟已滿或超出該檔案的長度,則write 函式返回失敗
在這裡插入圖片描述
在這裡插入圖片描述

lseek 函式

檔案讀寫指標定位到檔案描述符相應位置

在寫普通檔案時,寫操作從檔案的當前位移處開始
在這裡插入圖片描述

檔案讀寫鎖是什麼

在文 件已經共享的情況下如何操作,也就是當多個使用者共同使用、操作一個檔案的情況,這時, Linux 通常採用的方法是給檔案上鎖,來避免共享的資源產生競爭的狀態。

檔案鎖包括建議性鎖和強制性鎖。建議性鎖要求每個上鎖檔案的程式都要檢查是否有鎖 存在,並且尊重已有的鎖。在一般情況下,核心和系統都不使用建議性鎖。強制性鎖是由核心執行的鎖,當一個檔案被上鎖進行寫入操作的時候,核心將阻止其他任何檔案對其進行讀寫操作。採用強制性鎖對效能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。

在 Linux 中,實現檔案上鎖的函式有 lock 和 fcntl,其中 flock 用於對檔案施加建議性鎖, 而 fcntl 不僅可以施加建議性鎖,還可以施加強制鎖。同時,fcntl 還能對檔案的某一記錄進行 上鎖,也就是記錄鎖。

記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個程式都能在檔案的同一部分建立讀取鎖。而寫入鎖又稱為排斥鎖,在任何時刻只能有一個程式在檔案的 某個部分上建立寫入鎖。當然,在檔案的同一部分不能同時建立讀取鎖和寫入鎖。

fcntl 函式

不僅可施加建議性鎖,還可施加強制鎖
還能對檔案的某一記錄進行上鎖,即記錄鎖
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

//lock 的結構
Struct flock{ 
short l_type; 
off_t l_start; 
short l_whence; 
off_t l_len; 
pid_t l_pid; 
}

在這裡插入圖片描述

IO 多路複用

什麼是 IO 多路複用

一句話解釋:單執行緒或單程式同時監測若干個檔案描述符是否可以執行 IO 操作的能力。

解決什麼問題

應用程式通常需要處理來自多條事件流中的事件,比如我現在用的電腦,需要同時處理鍵盤滑鼠的輸入、中斷訊號等等事件,再比如 web 伺服器如 nginx,需要同時處理來來自 N 個客戶端的事件。

邏輯控制流在時間上的重疊叫做 併發

而 CPU 單核在同一時刻只能做一件事情,一種解決辦法是對 CPU 進行分時多工(多個事件流將 CPU 切割成多個時間片,不同事件流的時間片交替進行)。在計算機系統中,我們用執行緒或者程式來表示一條執行流,通過不同的執行緒或程式在作業系統內部的排程,來做到對 CPU 處理的分時多工。這樣多個事件流就可以併發進行,不需要一個等待另一個太久,在使用者看起來他們似乎就是並行在做一樣。

但凡事都是有成本的。執行緒 /程式也一樣,有這麼幾個方面:

  1. 執行緒 /程式建立成本
  2. CPU 切換不同執行緒 /程式成本 Context Switch
  3. 多執行緒的資源競爭

有沒有一種可以在單執行緒 /程式中處理多個事件流的方法呢?一種答案就是 IO 多路複用。

因此 IO 多路複用解決的本質問題是在用更少的資源完成更多的事

為了更全面的理解,先介紹下在 Linux 系統下所有 IO 模型。

I/O 模型

目前 Linux 系統中提供了 5 種 IO 處理模型

  1. 阻塞 IO
  2. 非阻塞 IO
  3. IO 多路複用
  4. 訊號驅動 IO
  5. 非同步 IO
阻塞 IO

這是最常用的簡單的 IO 模型。阻塞 IO 意味著當我們發起一次 IO 操作後一直等待成功或失敗之後才返回,在這期間程式不能做其它的事情。阻塞 IO 操作只能對單個檔案描述符進行操作,詳見readwrite

非阻塞 IO

我們在發起 IO 時,通過對檔案描述符設定 O_NONBLOCK flag 來指定該檔案描述符的 IO 操作為非阻塞。非阻塞 IO 通常發生在一個 for 迴圈當中,因為每次進行 IO 操作時要麼 IO 操作成功,要麼當 IO 操作會阻塞時返回錯誤 EWOULDBLOCK/EAGAIN,然後再根據需要進行下一次的 for 迴圈操作,這種類似輪詢的方式會浪費很多不必要的 CPU 資源,是一種糟糕的設計。和阻塞 IO 一樣,非阻塞 IO 也是通過呼叫read或 writewrite來進行操作的,也只能對單個描述符進行操作。

IO 多路複用

IO 多路複用在 Linux 下包括了三種,selectpollepoll,抽象來看,他們功能是類似的,但具體細節各有不同:首先都會對一組檔案描述符進行相關事件的註冊,然後阻塞等待某些事件的發生或等待超時。更多細節詳見下面的 “具體怎麼用”。IO 多路複用都可以關注多個檔案描述符,但對於這三種機制而言,不同數量級檔案描述符對效能的影響是不同的,下面會詳細介紹。

訊號驅動 IO

訊號驅動 IO是利用訊號機制,讓核心告知應用程式檔案描述符的相關事件。這裡有一個訊號驅動 IO 相關的例子

但訊號驅動 IO 在網路程式設計的時候通常很少用到,因為在網路環境中,和 socket 相關的讀寫事件太多了,比如下面的事件都會導致 SIGIO 訊號的產生:

  1. TCP 連線建立
  2. 一方斷開 TCP 連線請求
  3. 斷開 TCP 連線請求完成
  4. TCP 連線半關閉
  5. 資料到達 TCP socket
  6. 資料已經傳送出去(如:寫 buffer 有空餘空間)

上面所有的這些都會產生 SIGIO 訊號,但我們沒辦法在 SIGIO 對應的訊號處理函式中區分上述不同的事件,SIGIO 只應該在 IO 事件單一情況下使用,比如說用來監聽埠的 socket,因為只有客戶端發起新連線的時候才會產生 SIGIO 訊號。

非同步 IO

非同步 IO 和訊號驅動 IO 差不多,但它比訊號驅動 IO 可以多做一步:相比訊號驅動 IO 需要在程式中完成資料從使用者態到核心態(或反方向)的拷貝,非同步 IO 可以把拷貝這一步也幫我們完成之後才通知應用程式。我們使用aio_read 來讀,aio_write 寫。

同步 IO vs 非同步 IO

  1. 同步 IO 指的是程式會一直阻塞到 IO 操作如 read 、write 完成
  2. 非同步 IO 指的是 IO 操作不會阻塞當前程式的繼續執行

所以根據這個定義,上面阻塞 IO 當然算是同步的 IO,非阻塞 IO 也是同步 IO,因為當檔案操作符可用時我們還是需要阻塞的讀或寫,同理 IO 多路複用和訊號驅動 IO 也是同步 IO,只有非同步 IO 是完全完成了資料的拷貝之後才通知程式進行處理,沒有阻塞的資料讀寫過程。

select 函式

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

select 函式中的 timeout 是一個 struct timeval 型別的指標

struct timeval { 
 	long tv_sec; /* second */ 
 	long tv_unsec; /* and microseconds*/ 
}

程式

程式的定義

程式的概念首先是在 60 年代初期由 MIT 的 Multics 系統和 IBM 的 TSS/360 系統引入的。經過了 40 多年的發展,人們對程式有過各種各樣的定義。現列舉較為著名的幾種。

(1)程式是一個獨立的可排程的活動(E. Cohen,D. Jofferson)

(2)程式是一個抽象實體,當它執行某個任務時,將要分配和釋放各種資源(P. Denning)

(3)程式是可以並行執行的計算部分。(S. E. Madnick,J. T. Donovan)

以上程式的概念都不相同,但其本質是一樣的。它指出了程式是一個程式的一次執行的過程。它和程式是有本質區別的,程式是靜態的,它是一些儲存在磁碟上的指令的有序集合, 沒有任何執行的概念;而程式是一個動態的概念,它是程式執行的過程,包括了動態建立、排程和消亡的整個過程。它是程式執行和資源管理的最小單位。因此,對系統而言,當使用者在系統中鍵入命令執行一個程式的時候,它將啟動一個程式。

程式控制塊

程式是 Linux 系統的基本排程單位,那麼從系統的角度看如何描述並表示它的變化呢?在這裡,是通過程式控制塊來描述的。程式控制塊包含了程式的描述資訊、控制資訊以及資 源資訊,它是程式的一個靜態描述。在 Linux 中,程式控制塊中的每一項都是一個 task_struct結構,它是在 include/linux/sched.h 中定義的。

程式的標識

在 Linux 中最主要的程式標識有程式號(PID,Process Idenity Number)和它的父程式號 (PPID,parent process ID)。其中 PID 惟一地標識一個程式。PID 和 PPID 都是非零的正整數。 在 Linux 中獲得當前程式的 PID 和 PPID 的系統呼叫函式為 getpid 和 getppid,通常程式獲得當前程式的 PID 和 PPID 可以將其寫入日誌檔案以做備份。

fork 函式

fork 函式用於從已存在程式中建立一個新程式。新程式稱為子程式,而原程式稱為父程式。這兩個分別帶回它們各自的返回值,其中父程式的返回值是子程式的程式號,而子程式則返回 0。因此,可以通過返回值來判定該程式是父程式還是子程式。

使用 fork 函式得到的子程式是父程式的一個複製品,它從父程式處繼承了整個程式的地址空間,包括程式上下文、程式堆疊、記憶體資訊、開啟的檔案描述符、訊號控制設定、程式優先順序、程式組號、當前工作目錄、根目錄、資源限制、控制終端等,而子程式所獨有的只 有它的程式號、資源使用和計時器等。因此可以看出,使用 fork 函式的代價是很大的,它複製了父程式中的程式碼段、資料段和堆疊段裡的大部分內容,使得 fork 函式的執行速度並不很快。
在這裡插入圖片描述

exec 函式族

fork 函式是用於建立一個子程式,該子程式幾乎拷貝了父程式的全部內容,但是,這個新建立的程式如何執行呢?這個 exec 函式族就提供了一個在程式中啟動另一個程式執行的 方法。它可以根據指定的檔名或目錄名找到可執行檔案,並用它來取代原呼叫程式的資料段、程式碼段和堆疊段,在執行完之後,原呼叫程式的內容除了程式號外,其他全部被新的程式替換了。另外,這裡的可執行檔案既可以是二進位制檔案,也可以是 Linux 下任何可執行的 指令碼檔案。

在 Linux 中使用 exec 函式族主要有兩種情況:

  • 當程式認為自己不能再為系統和使用者做出任何貢獻時,就可以呼叫任何 exec 函式族 讓自己重生;

  • 如果一個程式想執行另一個程式,那麼它就可以呼叫 fork 函式新建一個程式,然後呼叫任何一個 exec,這樣看起來就好像通過執行應用程式而產生了一個新程式。
    在這裡插入圖片描述

查詢方式:

前 4 個函式的查詢方式都是完整的檔案目錄路徑,而最後2 個函式(也就是以 p 結尾的兩個函式)可以只給出檔名,系統就會自動從環境變數“$PATH” 所指出的路徑中進行查詢。

引數傳遞方式:

exec 函式族的引數傳遞有兩種方式:一種是逐個列舉的方式,而另一種則是將所有引數整體構造指標陣列傳遞。

在這裡是以函式名的第 5 位字母來區分的,字母為“l”(list)的表示逐個列舉的方式,其語法為 char arg;字母為“v”(vertor)的表示將所有引數整體構造指標陣列傳遞,其語法為const argv[]。讀者可以觀察 execl、execle、execlp 的語法與 execv、execve、execvp 的區別。

這裡的引數實際上就是使用者在使用這個可執行檔案時所需的全部命令選項字串(包括該可執行程式命令本身)。要注意的是,這些引數必須以 NULL 表示結束,如果使用逐個列舉方式,那麼要把它強制轉化成一個字元指標,否則 exec 將會把它解釋為一個整型引數,如果一個整型數的長度 char *的長度不同,那麼 exec 函式就會報錯。

環境變數 :

exec 函式族可以預設系統的環境變數,也可以傳入指定的環境變數。這裡以“e” (Enviromen)結尾的兩個函式 execle、execve 就可以在 envp[]中指定當前程式所使用的環境變數。
在這裡插入圖片描述

exec 函式族使用注意點:

在使用 exec 函式族時,一定要加上錯誤判斷語句。因為 exec 很容易執行失敗,其中最

常見的原因有:

  • 找不到檔案或路徑,此時 errno 被設定為 ENOENT;

  • 陣列 argv 和 envp 忘記用 NULL 結束,此時 errno 被設定為 EFAULT;

  • 沒有對應可執行檔案的執行許可權,此時 errno 被設定為 EACCES。

exit 和_exit 函式

exit 和__exit 函式都是用來終止程式的。當程式執行到 _exit 或_exit 時,程式會無條件地停 止剩下的所有操作,清除包括 PCB 在內的各種資料結構,並終止本程式的執行。但是,這兩 個函式還是有區別的,這兩個函式的呼叫過程如圖 所示
在這裡插入圖片描述

從圖中可以看出,_exit()函式的作用是:直接使程式停止執行,清除其使用的記憶體空間, 並清除其在核心中的各種資料結構;exit()函式則在這些基礎上作了一些包裝,在執行退出之 前加了若干道工序。exit()函式與_exit()函式最大的區別就在於 exit()函式在呼叫 exit 系統之前要檢查檔案的開啟情況,把檔案緩衝區中的內容寫回檔案,就是圖中的“清理 I/O 緩衝” 一項。

由於在 Linux 的標準函式庫中,有一種被稱作“緩衝 I/O(buffered I/O)”操作,其特徵 就是對應每一個開啟的檔案,在記憶體中都有一片緩衝區。每次讀檔案時,會連續讀出若干條 記錄,這樣在下次讀檔案時就可以直接從記憶體的緩衝區中讀取;同樣,每次寫檔案的時候, 也僅僅是寫入記憶體中的緩衝區,等滿足了一定的條件(如達到一數量或遇到特定字元等), 再將緩衝區中的內容一次性寫入檔案。

這種技術大大增加了檔案讀寫的速度,但也為程式設計帶來了一點麻煩。比如有一些資料, 認為已經寫入了檔案,實際上因為沒有滿足特定的條件,它們還只是儲存在緩衝區內,這時用_exit()函式直接將程式關閉,緩衝區中的資料就會丟失。因此,若想保證資料的完整性, 就一定要使用 exit()函式。
在這裡插入圖片描述

wait 和 waitpid 函式

wait 函式是用於使父程式(也就是呼叫 wait 的程式)阻塞,直到一個子程式結束或者該程式接到了一個指定的訊號為止。如果該父程式沒有子程式或者他的子程式已經結束,則 wait就會立即返回。

waitpid 的作用和 wait 一樣,但它並不一定要等待第一個終止的子程式,它還有若干選項,如可提供一個非阻塞版本的 wait 功能,也能支援作業控制。實際上 wait 函式只是 waitpid 函 數的一個特例,在 Linux 內部實現 wait 函式時直接呼叫的就是 waitpid 函式。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

waitpid 使用例項

本例中首先使用 fork新建一子程式,然後讓其子程式暫停 5s(使用了 sleep 函式)。接下來對原有的父程式使用 waitpid 函式,並使用引數 WNOHANG 使該父程式不會阻塞。若有子程式退出,則 waitpid返回子程式號;若沒有子程式退出,則 waitpid 返回 0,並且父程式每隔一秒迴圈判斷一次。
在這裡插入圖片描述

/*waitpid.c*/
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    pid_t pc, pr;
    pc = fork();
    if (pc < 0)
        printf("Error fork.\n");
    /*子程式*/
    else if (pc == 0)
    {
        /*子程式暫停 5s*/
        sleep(5);
        /*子程式正常退出*/
        exit(0);
    }
    /*父程式*/
    else
    {
        /*迴圈測試子程式是否退出*/
        do
        {
            /*呼叫 waitpid,且父程式不阻塞*/
            pr = waitpid(pc, NULL, WNOHANG);
            /*若子程式還未退出,則父程式暫停 1s*/
            if (pr == 0)
            {
                printf("The child process has not exited\n");
                sleep(1);
            }
        } while (pr == 0);
        /*若發現子程式退出,列印出相應情況*/
        if (pr == pc)
            printf("Get child %d\n", pr);
        else
            printf("some error occured.\n");
    }
}

Linux 守護程式

守護程式,也就是通常所說的 Daemon 程式,是 Linux 中的後臺服務程式。它是一個生 存期較長的程式,通常獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。守護程式常常在系統引導裝入時啟動,在系統關閉時終止。Linux 系統有很多守護程式,大多數服務都是通過守護程式實現的,如本書在第二章中講到的系統服務都是守護程式。同 時,守護程式還能完成許多系統任務,例如,作業規劃程式 crond、列印程式 lqd 等(這裡的 結尾字母 d 就是 Daemon 的意思)。

由於在 Linux 中,每一個系統與使用者進行交流的介面稱為終端,每一個從此終端開始運 行的程式都會依附於這個終端,這個終端就稱為這些程式的控制終端,當控制終端被關閉時, 相應的程式都會自動關閉。但是守護程式卻能夠突破這種限制,它從被執行開始運轉,直到整個系統關閉時才會退出。如果想讓某個程式不因為使用者或終端或其他的變化而受到影響,那麼就必須把這個程式變成一個守護程式。

建立一個守護程式

建立子程式,父程式退出

這是編寫守護程式的第一步。由於守護程式是脫離控制終端的,因此,完成第一步後就會在 Shell 終端裡造成一程式已經執行完畢的假象。之後的所有工作都在子程式中完成,而使用者在 Shell 終端裡則可以執行其他的命令,從而在形式上做到了與控制終端的脫離。父程式建立了子程式,而父程式又退出之後,此時該子程式不就沒有父程式了嗎?守護程式中確實會出現這麼一個有趣的現象,由於父程式已經先於子程式退出,會造成子程式沒有父程式,從而變成一個孤兒程式。在 Linux 中,每當系統發現一個孤兒程式,就會自動由 1 號程式(也就是 init 程式)收養它,這樣,原先的子程式就會變成 init 程式的子程式了。

關鍵程式碼:

/*父程式退出*/ 
pid=fork(); 
 if(pid>0){ 
 	exit(0); 
}
在子程式中建立新會話

這個步驟是建立守護程式中最重要的一步,雖然它的實現非常簡單,但它的意義卻非常重大。在這裡使用的是系統函式 setsid,在具體介紹 setsid 之前,讀者首先要了解兩個概念:程式組和會話期。

  • 程式組

程式組是一個或多個程式的集合。程式組由程式組 ID 來惟一標識。除了程式號(PID) 之外,程式組 ID 也一個程式的必備屬性。 每個程式組都有一個組長程式,其組長程式的程式號等於程式組 ID。且該程式 ID 不會 因組長程式的退出而受到影響。

  • 會話期

會話組是一個或多個程式組的集合。通常,一個會話開始於使用者登入,終止於使用者退出, 在此期間該使用者執行的所有程式都屬於這個會話期,它們之間的關係如下圖所示。 接下來就可以具體介紹 setsid 的相關內容:
在這裡插入圖片描述

(1)setsid 函式作用

setsid 函式用於建立一個新的會話,並擔任該會話組的組長。呼叫 setsid 有下面的 3 個

作用。

  • 讓程式擺脫原會話的控制

  • 讓程式擺脫原程式組的控制

  • 讓程式擺脫原控制終端的控制

(2)setsid 函式格式
在這裡插入圖片描述

改變當前目錄為根目錄

這一步也是必要的步驟。使用 fork 建立的子程式繼承了父程式的當前工作目錄。由於在程式執行過程中,當前目錄所在的檔案系統(比如“/mnt/usb”等)是不能解除安裝的,這對以後的使用會造成諸多的麻煩(比如系統由於某種原因要進入單使用者模式)。因此,通常的做法是 讓“/”作為守護程式的當前工作目錄,這樣就可以避免上述的問題,當然,如有特殊需要,也可以把當前工作目錄換成其他的路徑,如/tmp。改變工作目錄的常見函式是 chdir。

重設檔案許可權掩碼

檔案許可權掩碼是指遮蔽掉檔案許可權中的對應位。比如,有一個檔案許可權掩碼是 050,它 就遮蔽了檔案組擁有者的可讀與可執行許可權。由於使用 fork 函式新建的子程式繼承了父程式的檔案許可權掩碼,這就給該子程式使用檔案帶來了諸多的麻煩。因此,把檔案許可權掩碼設定為 0,可以大大增強該守護程式的靈活性。設定檔案許可權掩碼的函式是 umask。在這裡,通常的使用方法為 umask(0)。

關閉檔案描述符

同檔案許可權掩碼一樣,用 fork 函式新建的子程式會從父程式那裡繼承一些已經開啟了的檔案。這些被開啟的檔案可能永遠不會被守護程式讀或寫,但它們一樣消耗系統資源,而且 可能導致所在的檔案系統無法卸下。在上面的第二步之後,守護程式已經與所屬的控制終端失去了聯絡。因此從終端輸入的字元不可能達到守護程式,守護程式中用常規方法(如 printf)輸出的字元也不可能在終端上 顯示出來。所以,檔案描述符為 0、1 和 2 的 3 個檔案(常說的輸入、輸出和報錯這 3 個檔案) 已經失去了存在的價值,也應被關閉。

for(i=0;i<MAXFILE;i++) 
 	close(i);

建立守護程式的流程圖

在這裡插入圖片描述

相關文章