作者
雷鎮
本文是Linux系統調用系列文章的第一篇
對Linux系統調用的定義
基本原理
使用方法和注意事項大概作了一個介紹
以便讀者對Linux系統調用建立一個大致的印象
什麼是系統調用?
Linux內核中設置了一組用於實現各種系統功能的子程序
稱為系統調用
用戶可以通過系統調用命令在自己的應用程序中調用它們
從某種角度來看
系統調用和普通的函數調用非常相似
區別僅僅在於
系統調用由操作系統核心提供
運行於核心態
而普通的函數調用由函數庫或用戶自己提供
運行於用戶態
二者在使用方式上也有相似之處
在下面將會提到
隨Linux核心還提供了一些C語言函數庫
這些庫對系統調用進行了一些包裝和擴展
因為這些庫函數與系統調用的關系非常緊密
所以習慣上把這些函數也稱為系統調用
Linux中共有多少個系統調用?
這個問題可不太好回答
就算讓Linus Torvaldz本人也不見得一下子就能說清楚
在
版內核中
狹義上的系統調用共有
個
你可以在<內核源碼目錄>/include/asm
i
/unistd
h中找到它們的原本
也可以通過命令
man
syscalls
察看它們的目錄(man pages的版本一般比較老
可能有很多最新的調用都沒有包含在內)
廣義上的系統調用
也就是以庫函數的形式實現的那些
它們的個數從來沒有人統計過
這是一件吃力不討好的活
新內核不斷地在推出
每一個新內核中函數數目的變化根本就沒有人在乎
至少連內核的修改者本人都不在乎
因為他們從來沒有發布過一個此類的聲明
隨本文一起有一份經過整理的列表
它不可能非常全面
但常見的系統調用基本都已經包含在內
那裡面只有不多的一部分是你平時用得到的
本專欄將會有選擇的對它們進行介紹
為什麼要用系統調用?
實際上
很多已經被我們習以為常的C語言標准函數
在Linux平台上的實現都是靠系統調用完成的
所以如果想對系統底層的原理作深入的了解
掌握各種系統調用是初步的要求
進一步
若想成為一名Linux下編程高手
也就是我們常說的Hacker
其標志之一也是能對各種系統調用有透徹的了解
即使除去上面的原因
在平常的編程中你也會發現
在很多情況下
系統調用是實現你的想法的簡潔有效的途徑
所以有可能的話應該盡量多掌握一些系統調用
這會對你的程序設計過程帶來意想不到的幫助
系統調用是怎麼工作的?
一般的
進程是不能訪問內核的
它不能訪問內核所占內存空間也不能調用內核函數
CPU硬件決定了這些(這就是為什麼它被稱作
保護模式
)
系統調用是這些規則的一個例外
其原理是進程先用適當的值填充寄存器
然後調用一個特殊的指令
這個指令會跳到一個事先定義的內核中的一個位置(當然
這個位置是用戶進程可讀但是不可寫的)
在Intel CPU中
這個由中斷
x
實現
硬件知道一旦你跳到這個位置
你就不是在限制模式下運行的用戶
而是作為操作系統的內核
所以你就可以為所欲為
進程可以跳轉到的內核位置叫做sysem_call
這個過程檢查系統調用號
這個號碼告訴內核進程請求哪種服務
然後
它查看系統調用表(sys_call_table)找到所調用的內核函數入口地址
接著
就調用函數
等返回後
做一些系統檢查
最後返回到進程(或到其他進程
如果這個進程時間用盡)
如果你希望讀這段代碼
它在<內核源碼目錄>/kernel/entry
S
Entry(system_call)的下一行
如何使用系統調用?
先來看一個例子
#include /*定義宏_syscall
*/
#include /*定義類型time_t*/
_syscall
(time_t
time
time_t *
tloc) /*宏
展開後得到time()函數的原型*/
main()
{
time_t the_time;
the_time=time((time_t *)
); /*調用time系統調用*/
printf(
The time is %ld
the_time);
}
系統調用time返回從格林尼治時間
年
月
日
:
開始到現在的秒數
這是最標准的系統調用的形式
宏_syscall
()展開來得到一個函數原型
稍後我會作詳細解釋
但事實上
如果把程序改成下面的樣子
程序也可以運行得同樣的結果
#include
main()
{
time_t the_time;
the_time=time((time_t *)
); /*調用time系統調用*/
printf(
The time is %ld
the_time);
}
這是因為在time
h中實際上已經用庫函數的形式實現了time這個系統調用
替我們省掉了調用_syscall
宏展開得到函數原型這一步
大多數系統調用都在各種C語言函數庫中有所實現
所以在一般情況下
我們都可以像調用普通的庫函數那樣調用系統調用
只在極個別的情況下
我們才有機會用到_syscall*()這幾個宏
_syscall*()是什麼?
在unistd
h裡定義了
個宏
分別是
_syscall
(type
name)
_syscall
(type
name
type
arg
)
_syscall
(type
name
type
arg
type
arg
)
_syscall
(type
name
type
arg
type
arg
type
arg
)
_syscall
(type
name
type
arg
type
arg
type
arg
type
arg
)
_syscall
(type
name
type
arg
type
arg
type
arg
type
arg
type
arg
)
_syscall
(type
name
type
arg
type
arg
type
arg
type
arg
type
arg
type
arg
)
它們看起來似乎不太像宏
但其實質和
#define MAXSIZE
裡面的MAXSIZE沒有任何區別
它們的作用是形成相應的系統調用函數原型
供我們在程序中調用
我們很容易就能發現規律
_syscall後面的數字和typeN
argN的數目一樣多
事實上
_syscall後面跟的數字指明了展開後形成函數的參數的個數
讓我們看一個實例
就是剛剛用過的time系統調用
_syscall
(time_t
time
time_t *
tloc)
展開後的情形是這樣
time_t time(time_t * tloc)
{
long __res;
__asm__ volatile(
int $
x
:
=a
(__res) :
(
)
b
((long)(tloc)));
do {
if ((unsigned long)(__res) >= (unsigned long)(
)) {
errno =
(__res);
__res =
;
}
return (time_t) (__res);
} while (
) ;
}
可以看出
_syscall
(time_t
time
time_t *
tloc)展開成一個名為time的函數
原參數time_t就是函數的返回類型
原參數time_t *和tloc分別構成新函數的參數
事實上
程序中用到的time函數的原型就是它
errno是什麼?
為防止和正常的返回值混淆
系統調用並不直接返回錯誤碼
而是將錯誤碼放入一個名為errno的全局變量中
如果一個系統調用失敗
你可以讀出errno的值來確定問題所在
errno不同數值所代表的錯誤消息定義在errno
h中
你也可以通過命令
man
errno
來察看它們
需要注意的是
errno的值只在函數發生錯誤時設置
如果函數不發生錯誤
errno的值就無定義
並不會被置為
另外
在處理errno前最好先把它的值存入另一個變量
因為在錯誤處理過程中
即使像printf()這樣的函數出錯時也會改變errno的值
系統調用兼容性好嗎?
很遺憾
答案是
不好
但這決不意味著你的程序會三天兩頭的導致系統崩潰
因為系統調用是Linux的內核提供的
所以它們工作起來非常穩定
對於此點無需絲毫懷疑
在絕大多數的情況下
系統調用要比你自己編寫的代碼可靠而高效的多
但是
在Linux的各版本內核之間
系統調用的兼容性表現得並不像想象那麼好
這是由Linux本身的性質決定的
Linux是一群程序設計高手利用業余時間開發出來的
他們中間的大部分人沒有把Linux當成一個嚴肅的商業軟件
(現在的情況有些不同了
隨著Linux商業公司和以Linux為生的人的增長
不少人的腦筋發生了變化
)結果就是
如果新的方案在效率和兼容性上發生了矛盾
他們往往捨棄兼容性而追求效率
就這樣
如果他們認為某個系統調用實現的比較糟糕
他們就會毫不猶豫的作出修改
有些時候甚至連接口也一起改掉了
更可怕的是
很多時候
他們對自己的修改連個招呼也不打
在任何文檔裡都找不到關於修改的提示
這樣
每當新內核推出的時候
很可能都會悄悄的更新一些系統調用
用戶編制的應用程序也會跟著出錯
說到這裡
你是不是感覺前途一片昏暗呢?呵呵
不用太緊張
如前面所說
隨著越來越多的人把Linux當成自己的飯碗
不兼容的情況也越來越罕見
從
版本以後的Linux內核已經非常穩定了
不過盡管如此
你還是有必要在每個新內核推出之後
對自己的應用程序進行兼容性測試
以防止意外的發生
該如何學習使用Linux系統調用呢?
你可以用
man
系統調用名稱
的命令來查看各條系統調用的介紹
但這首先要求你要有不錯的英語基礎
其次還得有一定的程序設計和系統編程的功底
man pages不會涉及太多的應用細節
因為它只是一個手冊而非教程
如果man pages所提
From:http://tw.wingwit.com/Article/program/Oracle/201311/17062.html