linux系統調用(1)

來源:互聯網
上載者:User
本文是Linux系統調用系列文章的第一篇,對Linux系統調用的定義、基本原理、使用方法和注意事項大概作了一個介紹,以便讀者對Linux系統調用建立一個大致的印象。

什麼是系統調用?

Linux核心中設定了一組用於實現各種系統功能的子程式,稱為系統調用。使用者可以通過系統調用命令在自己的應用程式中調用它們。從某種角度來看,系統調用和普通的函數調用非常相似。區別僅僅在於,系統調用由作業系統核心提供,運行於核心態;而普通的函數調用由函數庫或使用者自己提供,運行於使用者態。二者在使用方式上也有相似之處,在下面將會提到。

隨Linux核心還提供了一些C語言函數庫,這些庫對系統調用進行了一些封裝和擴充,因為這些庫函數與系統調用的關係非常緊密,所以習慣上把這些函數也稱為系統調用。

Linux中共有多少個系統調用?

這個問題可不太好回答,就算讓Linus Torvaldz本人也不見得一下子就能說清楚。

在2.4.4版核心中,狹義上的系統調用共有221個,你可以在<核心源碼目錄>/include/asm-i386/unistd.h中找到它們的原本,也可以通過命令"man 2 syscalls"察看它們的目錄(man pages的版本一般比較老,可能有很多最新的調用都沒有包含在內)。廣義上的系統調用,也就是以庫函數的形式實現的那些,它們的個數從來沒有人統計過,這是一件吃力不討好的活,新核心不斷地在推出,每一個新核心中函數數目的變化根本就沒有人在乎,至少連核心的修改者本人都不在乎,因為他們從來沒有發布過一個此類的聲明。

隨本文一起有一份經過整理的列表,它不可能非常全面,但常見的系統調用基本都已經包含在內,那裡面只有不多的一部分是你平時用得到的,本專欄將會有選擇的對它們進行介紹。

為什麼要用系統調用?

實際上,很多已經被我們習以為常的C語言標準函數,在Linux平台上的實現都是靠系統調用完成的,所以如果想對系統底層的原理作深入的瞭解,掌握各種系統調用是初步的要求。進一步,若想成為一名Linux下編程高手,也就是我們常說的Hacker,其標誌之一也是能對各種系統調用有透徹的瞭解。

即使除去上面的原因,在平常的編程中你也會發現,在很多情況下,系統調用是實現你的想法的簡潔有效途徑,所以有可能的話應該盡量多掌握一些系統調用,這會對你的程式設計過程帶來意想不到的協助。

系統調用是怎麼工作的?

一般的,進程是不能訪問核心的。它不能訪問核心所佔記憶體空間也不能調用核心功能。CPU硬體決定了這些(這就是為什麼它被稱作"保護模式")。系統調用是這些規則的一個例外。其原理是進程先用適當的值填充寄存器,然後調用一個特殊的指令,這個指令會跳到一個事先定義的核心中的一個位置(當然,這個位置是使用者進程可讀但是不可寫的)。在Intel CPU中,這個由中斷0x80實現。硬體知道一旦你跳到這個位置,你就不是在限制模式下啟動並執行使用者,而是作為作業系統的核心--所以你就可以為所欲為。

進程可以跳轉到的核心位置叫做sysem_call。這個過程檢查系統調用號,這個號碼告訴核心進程請求哪種服務。然後,它查看系統調用表(sys_call_table)找到所調用的核心功能入口地址。接著,就調用函數,等返回後,做一些系統檢查,最後返回到進程(或到其他進程,如果這個進程時間用盡)。如果你希望讀這段代碼,它在<核心源碼目錄>/kernel/entry.S,Entry(system_call)的下一行。

如何使用系統調用?

先來看一個例子:


#include<linux/unistd.h> /*定義宏_syscall1*/#include<time.h>     /*定義類型time_t*/_syscall1(time_t,time,time_t *,tloc)    /*宏,展開後得到time()函數的原型*/main(){        time_t the_time;        the_time=time((time_t *)0); /*調用time系統調用*/        printf("The time is %ld/n",the_time);}

系統調用time返回從格林尼治時間1970年1月1日0:00開始到現在的秒數。

這是最標準的系統調用的形式,宏_syscall1()展開來得到一個函數原型,稍後我會作詳細解釋。但事實上,如果把程式改成下面的樣子,程式也可以運行得同樣的結果。


#include<time.h>main(){        time_t the_time;        the_time=time((time_t *)0); /*調用time系統調用*/        printf("The time is %ld/n",the_time);}

這是因為在time.h中實際上已經用庫函數的形式實現了time這個系統調用,替我們省掉了調用_syscall1宏展開得到函數原型這一步。

大多數系統調用都在各種C語言函數庫中有所實現,所以在一般情況下,我們都可以像調用普通的庫函數那樣調用系統調用,只在極個別的情況下,我們才有機會用到_syscall*()這幾個宏。

_syscall*()是什嗎?

在unistd.h裡定義了7個宏,分別是


_syscall0(type,name)_syscall1(type,name,type1,arg1)_syscall2(type,name,type1,arg1,type2,arg2)_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)

它們看起來似乎不太像宏,但其實質和


#define MAXSIZE 100

裡面的MAXSIZE沒有任何區別。

它們的作用是形成相應的系統調用函數原型,供我們在程式中調用。我們很容易就能發現規律,_syscall後面的數字和typeN,argN的數目一樣多。事實上,_syscall後面跟的數字指明了展開後形成函數的參數的個數,讓我們看一個執行個體,就是剛剛用過的time系統調用:


_syscall1(time_t,time,time_t *,tloc)

展開後的情形是這樣:


time_t   time(time_t *   tloc){    long __res;    __asm__ volatile("int $0x80" : "=a" (__res) : "0" (13),"b" ((long)(tloc)));    do {        if ((unsigned long)(__res) >= (unsigned long)(-125)) {            errno = -(__res);            __res  = -1;        }        return (time_t) (__res);    } while (0) ;}

可以看出,_syscall1(time_t,time,time_t *,tloc)展開成一個名為time的函數,原參數time_t就是函數的傳回型別,原參數time_t *和tloc分別構成新函數的參數。事實上,程式中用到的time函數的原型就是它。

errno是什嗎?

為防止和正常的傳回值混淆,系統調用並不直接返回錯誤碼,而是將錯誤碼放入一個名為errno的全域變數中。如果一個系統調用失敗,你可以讀出errno的值來確定問題所在。

errno不同數值所代表的錯誤訊息定義在errno.h中,你也可以通過命令"man 3 errno"來察看它們。

需要注意的是,errno的值只在函數發生錯誤時設定,如果函數不發生錯誤,errno的值就無定義,並不會被置為0。另外,在處理errno前最好先把它的值存入另一個變數,因為在錯誤處理過程中,即使像printf()這樣的函數出錯時也會改變errno的值。

系統調用相容性好嗎?

很遺憾,答案是--不好。但這決不意味著你的程式會三天兩頭的導致系統崩潰,因為系統調用是Linux的核心提供的,所以它們工作起來非常穩定,對於此點無需絲毫懷疑,在絕大多數的情況下,系統調用要比你自己編寫的代碼可靠而高效的多。

但是,在Linux的各版本核心之間,系統調用的相容性表現得並不像想象那麼好,這是由Linux本身的性質決定的。Linux是一群程式設計高手利用業餘時間開發出來的,他們中間的大部分人沒有把Linux當成一個嚴肅的商業軟體,(現在的情況有些不同了,隨著Linux商業公司和以Linux為生的人的增長,不少人的腦筋發生了變化。)結果就是,如果新的方案在效率和相容性上發生了矛盾,他們往往捨棄相容性而追求效率,就這樣,如果他們認為某個系統調用實現的比較糟糕,他們就會毫不猶豫的作出修改,有些時候甚至串連口也一起改掉了,更可怕的是,很多時候,他們對自己的修改連個招呼也不打,在任何文檔裡都找不到關於修改的提示。這樣,每當新核心推出的時候,很可能都會悄悄的更新一些系統調用,使用者編製的應用程式也會跟著出錯。

說到這裡,你是不是感覺前途一片昏暗呢?呵呵,不用太緊張,如前面所說,隨著越來越多的人把Linux當成自己的飯碗,不相容的情況也越來越罕見。從2.2版本以後的Linux核心已經非常穩定了,不過儘管如此,你還是有必要在每個新核心推出之後,對自己的應用程式進行相容性測試,以防止意外的發生。

該如何學習使用Linux系統調用呢?

你可以用"man 2 系統調用名稱"的命令來查看各條系統調用的介紹,但這首先要求你要有不錯的英語基礎,其次還得有一定的程式設計和系統編程的功底,man pages不會涉及太多的應用細節,因為它只是一個手冊而非教程。如果man pages所提供的東西不能使你感到非常滿意,那就跟我來吧,本專欄將向你展示Linux系統調用編程的無窮魅力。

對讀者的兩點小小的要求:

1)讀者必須有一定的C語言編程經驗;

2)讀者必須有一定的Linux使用經驗。如果你能完全看懂本文從開頭到這裡所講的東西,你就合格了。收拾好行囊,準備出發吧!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.