Linux進程編程基本概念

 

1.1 登錄

1.   用戶登錄名

登錄Linux系統時,需先鍵入用戶登錄名,然後鍵入用戶密碼,系統通過/etc/passwd(口令文件)文件校驗用戶登錄名和用戶密碼。口令文件中的登錄項由7個以冒號分隔的字段組成,分別爲登錄名、加密口令、數字用戶ID(224)、數字組ID(20)、註釋字段、起始目錄(/home/stevens)、以及Shell程序(/bin/sh)。

2.   登錄Shell

登錄後,系統先顯示一些典型的系統信息,然後就可以向Shell程序鍵入命令。Shell是一個命令行解釋器,它讀取用戶輸入,然後執行命令,用戶通常用終端,有時則通過Shell腳本文件向Shell進行輸入。常用的 Shell有:

Bourne Shell, /bin/sh

C Shell, /bin/csh

Korn Shell, /bin/ksh

系統從口令文件中登錄項的最後一個字段確定應該執行哪一種Shell。

Bourne Shell是其最早版本,也是最流行的,C Shell和Korn Shell是其後繼開發和升級的。三種Shell語法類似,功能基本相同。

 

1.2 文件和目錄

1.   文件系統

Linux文件系統是一種樹形層次結構,包括目錄和文件,目錄的起點稱爲根(root),其名字是一個字符/。

目錄(directory)是一個包含目錄項的文件,在邏輯上,可以認爲每個目錄項都包含一個文件名,同時還包含說明該文件屬性的信息。文件屬性有文件類型、文件長度、文件所有者、文件的許可權、文件最後的修改時間等,可用stat和fstat函數返回一個包含所有文件屬性的信息結構。

2.   文件名

目錄中的各個名字稱爲文件名,斜線 (/)不能出現在文件名中,斜線分隔是構成路徑名。當創建一個新目錄時,系統自動創建了兩個文件名,分別爲.(稱爲點 )和..(稱爲點-點),點引用當前目錄,點-點則引用父目錄,在最高層次的根目錄中,點-點與點相同。

3.   起始目錄

登錄時,工作目錄設置爲起始目錄(home directory),該起始目錄從口令文件中的登錄項中取得。起始目錄是創建用戶時創建的,如test的起始目錄爲/home/test。起始目錄又稱爲主目錄,用“~”可表示該用戶的主目錄。

4.   路徑名

0個或多個以斜線分隔的文件名序列構成路徑名(pathname),以斜線開頭的路徑名稱爲絕對路徑名(absolute pathname ),否則稱爲相對路徑名(relative pathname)。如當前目錄爲/home/test/hello,cd  /home/test/hello/why是使用絕對路徑方式到達該目錄,cd  ./why是使用相對路徑到達該目錄,cd  ~/hello/why是通過主目錄到達該目錄。

5.   工作目錄

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

登錄一個用戶時就有一個工作目錄,用cd命令可以改變工作目錄,如登錄test用戶後敲入cd hello,其工作目錄變爲/home/test/hello。

 

1.3 輸入和輸出

1.   文件描述符

文件描述符是一個非負整數,內核用來標識一個特定進程正在訪問的文件。當內核打開一個現存文件或創建一個新文件時,它就返回一個文件描述符。

每個進程在Linux內核中都有一個task_struct結構體來維護進程相關的信息,稱爲進程描述符(Process Descriptor),又稱爲進程控制塊(PCB,Process  Control Block)。task_struct中有一個指針指向files_struct結構體,稱爲文件描述符表,其中每個表項包含一個指向已打開文件的指針,如下圖12-1所示。

進程控制塊(task_struct)指向文件指針的順序爲task_struct-> files(file_struct) ->fd數組,fd數組大小決定了進程打開的最大文件個數。

圖12-1 文件描述符圖

用戶程序不能直接訪問內核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3這些數字),這些索引就稱爲文件描述符(File  Descriptor),int型變量保存。當調用open打開一個文件或創建一個新文件時,內核分配一個文件描述符並返回給用戶程序,該文件描述符表項中的指針指向新打開的文件。當讀寫文件時,用戶程序把文件描述符傳給readwrite,內核根據文件描述符找到相應的表項,再通過表項中的指針找到相應的文件。

2.   標準輸入、標準輸出和標準出錯

每當運行一個新程序(進程,包括Shell程序產生的進程)時,進程自動打開三個文件描述符,即標準輸入、標準輸出以及標準出錯。Shell中0表示標準輸入(stdin),1表示標準輸出(stdout),2表示標準錯誤(stderr)。

標準輸入對應鍵盤,標準輸出對應屏幕,標準錯誤同樣對應屏幕。

Shell都提供一種方法,使任何一個或所有這三個描述符都能重新定向到某一個文件。例如:ls>file.list,就是將標準輸出重新定向到名爲file.list的文件上。

3.   不用緩存的I/O

函數open、read、write、lseek以及close提供了不用緩存的I/O,這些函數都是用文件描述符進行工作。不用緩存的I/O函數一次讀寫操作完成一次系統調用,當初級文件I/O寫函數write所帶的寫大小參數太小時,會引起系統調用次數過多而造成系統效率低下。

4.   標準I/O

標準I/O函數讀寫時無需關心緩存大小,操作系統對標準I/O函數自動分配緩存、使用緩存和管理緩存。使用標準I/O可無需擔心如何選取最佳的緩存長度,例如fread、fwrit、fprintf、fscanf、fgets等都是標準I/O,標準I/O是不用緩存的I/O的發展,這些函數默認都使用了緩存。

1.4程序與進程

1.   程序

可執行程序是存放在磁盤文件中的可執行文件,可使用6個exec函數中的一個由內核將程序讀入內存,並使其執行。

2.   進程和進程 ID

程序的執行實例被稱爲進程(process)。進程空間(包括代碼段空間、數據區空間、運行的堆棧空間)存在於內存中。CPU執行的是進程代碼段的代碼,進程執行時間就是CPU執行該進程代碼的時間。程序是靜態的,存放在硬盤上,是永久的;而進程是動態的,是暫時的,有其生命週期,當程序加載到內存時,操作系統爲其分配好其進程空間時,從main函數開始運行時,標誌着該進程生命的開始,當調用exit函數退出時標誌着該進程生命的結束,隨後操作系統回收進程空間和所佔資源。

每個Linux進程都一定有一個唯一的數字標識符,稱爲進程ID(process ID)。進程ID總是一非負整數,getpid函數可得到本進程的進程ID號。

下面是getpid的函數範例,getpid.c源代碼如下:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

int main()

{

    printf("pid=%d\n", getpid()) ;

    printf("parent pid=%d\n", getppid()) ;

    return 0 ;

}

編譯 gcc getpid.c -o getpid。

執行./getpid,執行結果如下:

pid=6307

parent pid=5441

使用 ps  -ef|grep 5441,得到如下結果:

zjkf      5441  5438  0 06:20 pts/0    00:00:00 -bash

其中getpid.c就是源程序,getpid執行碼就是編譯後的可執行程序,./getpid執行碼執行的過程叫做進程。可見,不管源程序,還是編譯後的可執行程序,都是靜態的,而進程是動態的。getpid進程的父進程爲Shell(bash)進程,Shell進程是所有在終端上執行進程的父進程。

3.   進程控制

用於進程控制的主要函數有fork、exec(exec函數有六種變體,但經常把它們統稱爲exec函數)和waitpid。

 

1.5 ANSI C

American National Standards Institute(ANSI——美國國家標準學會)是由公司、政府和其他成員組成的自願組織。它們協商與標準有關的活動,審議美國國家標準,並努力提高美國在國際標準化組織中的地位。

由於美國在計算機早期發展中一直處於領先地位,因此ANSI的很多標準已經成爲事實上的國際標準。其中常見的ANSI ASCII字符編碼幾乎爲所有的編碼方式所兼容。

標準的力量無處不在,標準是一種約定和約束,提供了技術目標說明與技術規範,標準的導向性和強制性加快了技術交流和提高了技術質量。

1.   ANSI C

符合ANSI標準的C語言稱爲ANSI C,現在的C語言程序一般都符合ANSI C標準。

2.   ANSI  C函數原型

標準庫函數的函數原型都在頭文件中提供,程序可以用#include指令包含這些原型文件。對於用戶自定義函數,程序員需要在頭文件或文件頭聲明其原型。對於返回int型和void型且不帶形參的C語言函數可以不聲明其函數原型,但不鼓勵這麼用。

函數原型描述了函數到編譯器的接口,編譯程序在編譯時就可以檢查在調用函數時是否使用了正確的參數。

如getpid函數原型如下,其中pid_t是一個typedef定義類型,其定義爲typedef  int pid_t。

pid_t   getpid ( void ) ;

調用帶參數的getpid(如getpid(1)),則ANSI C程序將給出下列形式的出錯信息:

line   8: too  many arguments to  function  " getpid "

另外,因爲編譯程序知道參數的數據類型,所以如果可能,它就會將參數強制轉換成所需的數據類型。

3.   ANSI  C類屬指針

在標準ANSI中,read和write的第二個參數現在是void  *類型,而早期的 Unix(Linux是Unix的後繼者,站在Unix的肩膀上)系統都使用char *這種指針類型。

ANSI C使用void *作爲類屬指針來代替char *,這樣函數原型和類屬指針的組合消去了很多非ANSI C程序需要的顯式類型強制轉換。

例如,下面代碼使用ANSI C標準,可以寫成:

float data[100];

write(fd,data,sizeof(data)) ;

若使用非ANSI C編譯程序,則需寫成:

write( fd , (void  *)data , sizeof(data)) ;

 

1.6 用戶標識

1.   用戶ID

在Linux系統,每個用戶有一個唯一的用戶ID號。

口令文件登錄項中的用戶ID(user ID)是個數值,它向系統標識各個不同的用戶。系統管理員在確定一個用戶登錄名的同時,確定其用戶ID,用戶不能更改其用戶ID,通常每個用戶有一個唯一的用戶ID。

用戶ID爲0的用戶爲根 (root)或超級用戶 (superuser)。在口令文件中,通常有一個登錄項,其登錄名爲root,root用戶屬於特權用戶。如果一個進程具有超級用戶特權,則大多數文件許可權檢查都不再進行。某些操作系統功能只限於向超級用戶提供,超級用戶對系統有自由的支配權。

2.   組ID

在Linux系統,每個用戶有一個唯一的組ID號。

口令文件登錄項也包括用戶的組 ID(group ID),它也是一個數值。組 ID也是由系統管理員在確定用戶登錄名時分配的。

組文件將組名映射爲數字組 ID,它通常是/etc/group。

一個用戶可以屬於多個組,但只能屬於一個主組,非主組又稱爲添加組或附屬組。

getuid.c源代碼如下:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

int main()

{

    printf("uid=%d, gid=%d\n", getuid(), getgid()) ;

    return 0 ;

}

編譯 gcc getuid.c -o getuid。

執行./getuid,執行結果如下:

uid=1008, gid=1003

 

1.7 出錯處理

當Linux函數出錯時,經常會給整型變量errno設置一個值,error中每個值都表示特定的含義。

文件<errno.h>中定義了變量errno以及可以賦予它的各種常數宏定義,這些常數都以E開頭,如errno等於常數EACCES時,表示“此進程沒有打開該文件的權限”。

對於errno有兩條規則:第一條規則是如果沒有出錯,則error的值不會被重設;因此,僅當函數的返回值指明出錯時,才檢驗其值。第二條是任一函數都不會將errno值設置爲0,在<errno.h>中定義的所有常數都不爲0。

C標準定義了兩個函數,即strerror和perror,它們幫助打印出錯信息,這兩個函數的函數原型如下:

# include   <string.h>

char   *strerror(int  errnum);

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

perror函數在標準出錯上產生一條出錯消息(基於errno當前值),然後返回。

# include   <stdio.h>

void   perror( const   char  *msg);

它首先輸出由msg指向的字符串,然後是一個冒號、一個空格、然後是對應errno值的出錯信息,最後是一個換行符。

 

下面以兩個範例來說明strerror和perror的使用。

strerror.c源代碼如下,此代碼打印出0到131的錯誤原因描述。

#include <stdio.h>

#include <string.h>

int main()

{

    int i;

    for(i=0;i<132;i++)

    {

        printf("%d : %s\n", i, strerror(i));

    }

    return 0 ;

}

編譯 gcc strerror.c -o strerror。

執行./strerror,執行結果如下:

0 : Success

1 : Operation not permitted

2 : No such file or directory

……

 

perror.c源代碼如下,此函數打印出錯誤原因信息字符串。

#include <stdio.h>

int main()

{

    FILE *fp;

    fp = fopen("/tmp/noexist", "r+") ;

    if ( fp == NULL )

    {

        perror("fopen") ;

        return -1 ;

    }

    return 0 ;

}

編譯 gcc peror.c –o perror。

執行./perror,執行結果如下:

fopen: No such file or directory

 

           摘錄自《深入淺出Linux工具與編程》

       

發佈了47 篇原創文章 · 獲贊 61 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章