日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 编程中的API函数和系统调用的关系【转】

發布時間:2025/7/14 linux 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 编程中的API函数和系统调用的关系【转】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉自:http://blog.chinaunix.net/uid-25968088-id-3426027.html

原文地址:Linux 編程中的API函數和系統調用的關系?作者:up哥小號

?

API:(Application Programming Interface,應用程序編程接口)
??指的是我們用戶程序編程調用的如read(),write(),malloc(),free()之類的調用的是glibc庫提供的庫函數。API直接提供給用戶編程使用,運行在用戶態。
? 我們經常說到的POSIX(Portable Operating System Interface of Unix)是針對API的標準,即針對API的函數名,返回值,參數類型等。POSIX兼容也就指定這些接口函數兼容,但是并不管API具體如何實現。
系統調用
??通過軟中斷或系統調用指令向內核發出一個明確的請求,內核將調用內核相關函數來實現(如sys_read(),sys_write(),sys_fork())。用戶程序不能直接調用這些Sys_read,sys_write等函數。這些函數運行在內核態。 兩者關系: 通常API函數庫(如glibc)中的函數會調用封裝例程,封裝例程負責發起系統調用(通過發軟中斷或系統調用指令),這些都運行在用戶態。內核開始接收系統調用后,cpu從用戶態切換到內核態(cpu處于什么狀態,程序就叫處于什么狀態,所以很多地方也說程序從用戶態切換到內核態,實際是cpu運行級別的切換,通常cpu 運行在3級表示用戶態,cpu 運行在0級表示內核態),內核調用相關的內核函數來處理再逐步返回給封裝例程,cpu進行一次內核態到用戶態的切換,API函數從封裝例程拿到結果,再處理完后返回給用戶。
?但是API函數不一定需要進行系統調用,如某些數學函數,沒有必要進行系統調用,直接glibc里面就給處理了,整個過程運行在用戶態。
? 所以作為我們編寫linux用戶程序的時候,是不能直接調用內核里面的函數的,內核里面的函數位于進程虛擬地址空間里面的內核空間,用戶空間函數及函數庫都處于進程虛擬地址空間里面的用戶空間,用戶空間調用內核空間的函數只有一個通道,這個通道就是系統調用指令,所以通常要調用glibc等庫的接口函數,glibc也是用戶空間的,但glibc自己實現了調用特殊的宏匯編系統調用指令進行cpu運行狀態的切換,把進程從用戶空間切換到內核空間。
用戶態函數執行全過程(這里只講需要進行系統調用的函數) ?用戶態xyz()函數,內核最終一般會調用形如sys_xyz()的服務例程來處理(不過也有一些例外,這里暫時不考慮) ? 函數xyz()是直接提供給用戶編程使用的。圖中“SYSCALL”,“SY***IT”表示真正的匯編指令(匯編指令具體調用的是哪個暫時不關心,我們只需在此關注發起和退出了一個系統調用)。
?發起系統調用:xyz()函數執行的過程中會執行SYSCALL匯編指令,此指令將會把cpu從用戶態切換到內核態。SYACALL匯編指令中會包含將要調用的內核函數的系統調用號和參數,內核在上圖系統調用處理程序中去查一個sys_call_talbe數組來找到這個系統調用號對應的服務例程(如sys_xyz())函數的地址,然后調用這個地址的函數執行。(這里glibc里面的系統調用號和內核里面的系統調用號必須完全相等,當然,這是約定好的)
? 系統調用返回:服務例程(如sys_xyz())函數返回值一般返回正數和0表示系統調用成功結束,而負數表示一個出錯條件。緊接著SY***IT退出系統調用,此指令將cpu從內核態切換到用戶態,glibc針對系統調用返回值如果出錯則需要設置好errno(通常在c庫頭文件/usr/include/errno.h中),然后返回一個值做為glibc封裝例程的返回值(如xyz()的返回值)。這里errno是libc自己用來定義的出錯碼,不一定是最后gblic封裝例程的返回值

這里涉及到幾個概念需要好好講講:

1.系統調用號

為了把系統調用號和相應的服務例程關聯起來,如64位系統中
cat /usr/include/asm/unistd_64.h
#ifndef __SYSCALL
#define __SYSCALL(a, b)
#define __NR_read?????????????????????????????? 0
__SYSCALL(__NR_read, sys_read)
#define __NR_write????????????????????????????? 1
等等
__SYSCALL(__NR_write, sys_write)
#define __NR_dup??????????????????????????????? 32
__SYSCALL(__NR_dup, sys_dup)
#define __NR_dup2?????????????????????????????? 33
__SYSCALL(__NR_dup2, sys_dup2)
等等
Glibc和內核里面的這個系統調用號是一致的,所以glibc調用匯編之類把系統調用號傳給內核的時候,內核通過自己的系統調用分派表sys_call_table(可以理解為一個系統調用號,對應一個函數入口地址)找到這個具體的系統調用服務例程對應的函數入口地址,如上面sys_read,sys_write等

2.參數傳遞

??? 在發起系統調用前,eax寄存器里面存儲了系統調用號。如用戶程序fork()函數,glibc 發出int 0x80或sysenter指令前,eax寄存器就會設置好內核的sys_fork函數對應的系統調用號,這是glibc里面的封裝例程會自動設置好的,程序員無需關心。
?? ?有些系統調用可能調用很多參數(除了系統調用號之外),普通c函數的參數傳遞是通過把參數值寫入活動的程序棧(用戶態棧或者內核態棧)實現的。因為系統調用是一種跨用戶態和內核態的特殊函數,所以這兩個棧都不能用。在發出系統調用之前,系統調用的參數寫入了cpu的寄存器(如glibc去寫好這些寄存器),然后發出系統調用之后,而在內核調用服務例程(如sys_fork()服務例程)之前,內核再把存放在cpu中的參數拷貝的內核態的堆棧中(因為sys_fork只是普通的c函數,前面說過普通c函數的參數傳遞是通過把參數值寫入活動的程序棧(用戶態棧或者內核態棧)實現的)。內核為什么不直接把用戶態的棧拷貝到內核態的棧而要去通過寄存器來傳呢?首先,同事操作兩個棧是比較復雜的,其次,寄存器的使用使得系統調用處理程序的結構與其它異常處理程序的結構類似。
??? 使用寄存器傳遞參數,必須滿足兩個條件:
????每個參數的長度不能超過寄存器的長度(比如寄存器長度32位,那參數長度就不能超過32位);
??? 參數的個數不能超過6個(除了eax中傳遞的系統調用號),因為80x86處理器的寄存器的數量是有限的。
??? 第一個條件總能成立,因為POSIX標準規定,如果寄存器里面裝不下那個長度的參數,那么必須改用參數的地址來傳遞。
??? 第二個條件有的系統調用參數大于6個,這種情況下,必須用一個單獨的寄存器執行進程地址空間的這些參數所在的一個內存區。
??? 存放系統調用號和系統調用參數的寄存器是eax(存放系統調用號),ebx,ecx,edx,esi,edi,ebp

3.SYSCALL,SY***IT進入退出系統調用

????這里SYSCALL,SY***IT只是個代號,具體匯編指令如下
??? 進入系統調用:內核2.6以前通過int $0x80匯編指令;內核2.6以后sysenter匯編語言指令。
??? 退出系統調用:舊的iret匯編指令,新的sy***it指令

??? 現在內核同時支持這兩類新舊指令
??? 向量128(0x80)對應于內核入口點,在內核初始化期間調用的函數trap_init(0,用以下方式建立對應于向量128的中斷描述符表項set_system_gate(0x80,&system_call).
??? 當用戶態進程發出int $0x80指令時,cpu切換到內核態并開始從地址system_call處開始執行指令。System_call()函數首先把系統調用號和這個異常處理程序可以用到的所有cpu寄存器保存到相應的內核棧中,不包括由控制單元已自動保存的eflags,cs,eip,ss,esp寄存器。隨后,在ebx中存放當前進程的thread_info數據結構的地址,這是通過獲得內核棧指針的值并把它取整到4kb或8kb的倍數而完成的。然后檢查thread_info結構flag字段的TIF_SYSCALL_TRACE和TIF_SYSCALL_AUDIT標識之一是否被設置為1,也就是檢查是否有某一調試程序正在跟蹤執行程序對系統調用的調用。如果被置1,那么system_call()函數兩次調用do_syscall_trace()函數:一次正好在這個系統調用服務例程執行之前,一次在其之后。Do_syscall_trace函數停止current,并因此允許調試進程收集關于current的信息。

????系統調用退出:
??? (1)用戶態的寄存器剛進來到系統調用的時候被保存到了內核棧中,錯誤的返回值會被寫的剛開始傳遞系統調用號的那個eax寄存器所在棧的位置。(那么將來當用戶態恢復執行的時候,eax寄存器里面的內容就是系統調用的返回碼了。)
??? (2)禁止本地中斷,并檢查current的thread_info結構中的標志。如果有任何標志被設置,那么在返回到用戶態之前還需要完成一些工作。

用source insight在linux內核中查找sys_open函數
#define __NR_open?????????????????????????????? 2
__SYSCALL(__NR_open, sys_open)
剛開始我搜索“sys_open”,苦逼的找了幾遍沒找到具體實現的地方,都是調用這個函數的地方。。后經伯松提醒,可能被宏給替換了。。后想起代碼中出現過的SYSCALL_DEFINE宏,就進行了給name加上”sys_”前綴,所以找到SYSCALL_DEFINE中含有open的這句

點擊(此處)折疊或打開

  • SYSCALL_DEFINE3(open,?const?char __user?*,?filename,?int,?flags,?umode_t,?mode)
  • {
  • ?long ret;
  • ?if?(force_o_largefile())
  • ??flags?|=?O_LARGEFILE;
  • ?ret?=?do_sys_open(AT_FDCWD,?filename,?flags,?mode);
  • ?/*?avoid REGPARM breakage?on?x86:?*/
  • ?asmlinkage_protect(3,?ret,?filename,?flags,?mode);
  • ?return ret;
  • }
  • 深刻懷疑SYSCALL_DEFINE,其定義如下(在include/linux/syscalls.h中)
    #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)? //注意,這里name已經變成了”_name”,加上了下劃線了,所以”open”變成了“_open”了

    點擊(此處)折疊或打開

  • #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
  • #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
  • #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
  • #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
  • #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
  • 進一步
    #define SYSCALL_DEFINEx(x, sname, ...)??? __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)//前面name已經加上“_open”了,記住
    進一步
    #define __SYSCALL_DEFINEx(x, name, ...)?? asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))//這里,變成了“sys_open”了
    所以,這里變成了 asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))
    而再進一步
    #define __SC_DECL1(t1, a1)?t1 a1
    #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
    #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__),這里變成了asmlinkage long sys_open(const char __user*? filename,__SC_DECL2(__VA_ARGS__))
    進一步變成了
    asmlinkage long sys_open(const char __user*? fliename,int flags,__SC_DECL1(t1,a1))
    進一步變成了
    asmlinkage long sys_open(const char __user*? filename,int flags,umode_t mode)了!
    所以,最終變成了

    點擊(此處)折疊或打開

  • asmlinkage long sys_open(const char __user* filename,int flags,umode_t mode){
  • long ret;
  • if (force_o_largefile())
  • flags |= O_LARGEFILE;
  • ret = do_sys_open(AT_FDCWD, filename, flags, mode);
  • /* avoid REGPARM breakage on x86: */
  • asmlinkage_protect(3, ret, filename, flags, mode);
  • return ret;
  • }
  • 補充說明:宏定義中出現的#,##,...和__VA_ARGS_的特殊說明
    # ?
    這個是一個字符串替換,如#define S(x)?? “a”#x
    S(1),則變成了字符串“a1”了
    ##,這個是個簡單的替換
    如#define T(x)? a##x
    T(1)則變成了 a1 了,比如你前面定義了int a1=2; 就可以printf”%d”,T(1)),即等價于printf(“%d”,a1);如果你用來上面的S(1)替換T(1)那就變成了printf(“%d”,“a1”)肯定就不對了,或者你#define S(x) a#x,用S(1)替換T(1)那就變成了printf(“%d”,a”1”)了,肯定也不對了,所以,用“##”有的時候也是必須 的

    宏定義的參數,如#define X(...) printf(“%s %s”,__VA_ARGS__);
    X(“a”,”b”)就變成了printf(“%s %s”,a,b);了
    __VA_ARGS__就表示吧...參數給完整替換掉,”__VA_ARGS__”這個字符串缺一個字符都不可以。。
    另外“#,##,...和__VA_ARGS_”也可參看
    http://www.cnblogs.com/zhujudah/archive/2012/03/22/2411240.html
    的一些講解。 測試例程:

    點擊(此處)折疊或打開

  • #include
  • #define S(x) "a"#x
  • #define T(x) a##x
  • #define M(x) x
  • #define N(x) (x)
  • int main(){
  • int a1=2;
  • printf("%d\n",T(1));
  • printf("%s\n",S(1));
  • printf("%d\n",M(1));
  • printf("%d\n",N(1));
  • }
  • 輸出
    2
    a1
    1
    1
    可以看到宏中帶括號和不帶括號是一樣的效果
    參考
    1.《深入理解linux內核(第三版)》,
    2.? kernel-3.6.7源碼
    3.http://www.cnblogs.com/zhujudah/archive/2012/03/22/2411240.html











    本文轉自張昺華-sky博客園博客,原文鏈接:http://www.cnblogs.com/sky-heaven/p/5708423.html,如需轉載請自行聯系原作者

    總結

    以上是生活随笔為你收集整理的Linux 编程中的API函数和系统调用的关系【转】的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 五月天婷婷在线播放 | 97久草| 免费成人在线看 | 亚洲综合一区二区三区 | 少妇一边呻吟一边说使劲视频 | 久久不卡影院 | 女人性做爰24姿势视频 | 成人午夜精品福利免费 | 91综合久久 | 免看一级a毛片一片成人不卡 | 日韩黄色精品视频 | 亚洲天堂网一区 | 成人资源在线观看 | 午夜影院免费视频 | 精品无码一区二区三区电影桃花 | 国产日产亚洲系列最新 | 神马久久久久久久久久久 | 高潮av| 精品96久久久久久中文字幕无 | 台湾佬美性中文 | missav|免费高清av在线看 | 欧洲免费av | 欧美性xxxxxxxxx | 在线播放国产一区 | 肥臀熟女一区二区三区 | 欧美另类一区 | 综合久久久久久 | 欧美人妻一区二区三区 | 亚洲精品污 | 欧美打屁股 | 日韩avav | 97插插插 | 欧美国产精品久久 | 波多野吉衣一区二区 | 国产精品美女久久久 | 日本午夜三级 | 免费三级在线 | 国产suv精品一区二区 | 国产视频xxxx | 进去里视频在线观看 | www久久| 欧美与黑人午夜性猛交久久久 | 伊人狼人综合 | 在线观看中文字幕av | 久久精品中文 | 人妻洗澡被强公日日澡 | 日韩综合av | 国产一区二区三区免费 | 51精品国自产在线 | 天天摸天天碰天天爽天天弄 | 青草草在线 | 天天干天天操天天干 | 少妇献身老头系列 | 日韩一区二区影院 | 亚洲春色一区二区三区 | 日韩欧美视频在线 | 十八岁世界在线观看高清免费韩剧 | 少妇肥臀大白屁股高清 | 国产综合av | 欧美一区二区三区在线免费观看 | 色婷婷一区二区 | 久久久精品国产免费爽爽爽 | 美女的奶胸大爽爽大片 | 中文字幕天堂在线 | 健身教练巨大粗爽gay视频 | 波多野结衣一二三四区 | 国产一区二区三区播放 | 欧美丝袜一区二区 | 亚洲精品视频在线免费 | 中文字幕乱码一二三区 | 毛片在哪里看 | av片子在线观看 | 国产日韩一级片 | 尹人在线视频 | 高潮毛片无遮挡高清免费 | 三级网站在线免费观看 | 午夜免费激情视频 | 日韩少妇 | 午夜久久久久久噜噜噜噜 | 99黄色片 | 国产外围在线 | 国语对白做受欧美 | 九色视频网站 | 北岛玲一区二区 | 国产人免费人成免费视频 | 激情自拍偷拍 | 日韩成人综合网 | 亚洲 小说区 图片区 都市 | 日本三级视频在线观看 | 网站av在线 | 久久99这里只有精品 | 做a爰小视频 | 亚洲v天堂 | 白丝av | 麻豆精品自拍 | 国产精品久久久久久久无码 | 亚洲综合久久网 | 免费播放片大片 | 99久久免费国产精精品 |