系統調用與標準函數庫(下)

        友情提示:本文系接上一篇博文——系統調用與標準函數庫(上)

        2. 系統調用與內核

        爲了更好地保護了內核,在Linux中,把程序運行空間分爲內核空間和用戶空間,它們分別運行在不同的級
別上。用戶進程在通常情況下不允許訪問內核數據,也無法使用內核函數,但在有些情況下,就比如本人最近做的GPON項目中,應用程序經常需要與內核打交道,這個時候用戶空間的進程需要獲得一定的系統服務,這時,就必須通過系統調用。

        應用程序運行在用戶空間,系統調用需要切換到內核空間,應用程序應該以某種方式通知內核需要切換到內核空間。通知內核的機制是靠軟件中斷實現的:應用程序執行異常指令,引發一個異常,程序進入中斷,系統切換到內核態去執行異常處理程序。此處的異常處理程序即系統調用處理程序syscall()。所有的系統調用陷入內核的方式都一樣,所以僅僅是陷入內核空間是不夠的,必須以某種方式通知內核進入異常的原因。Unix系統通過系統調用號通知內核進入異常的原因,操作系統給每個系統調用分配了一個唯一的編號,這個編號就是系統調用號。用戶空間進程執行一個系統調用時,這個系統調用號就被用來指明執行哪個系統調用。系統調用號相當關鍵,一旦分配就不能再有任何變更,否則編譯好的應用程序會崩潰。此外,如果一個系統調用被刪除,它所佔用的系統調用號也不允許被回收利用。(路徑:/usr/include/i386-linux-gnu/asm/unistd_32.h)

                          


        3. 系統調用與庫

        庫函數由兩類函數組成:第一類是不需要調用系統調用的:不需要切換到內核空間即可完成函數全部功能,
並且將結果反饋給應用程序,如strcpy、bzero等字符串操作函數。第二類是需要調用系統調用:需要切換到內核空間,這類函數通過封裝系統調用去實現相應功能,如printf、fread等。

        庫函數與系統調用的關係:並不是所有的系統調用都被封裝成了庫函數,系統提供的很多功能都必須通過系統調用才能實現。系統調用是需要時間的,程序中頻繁的使用系統調用會降低程序的運行效率。當運行內核代碼時,CPU工作在內核態,在系統調用發生前需要保存用戶態的棧和內存環境,然後轉入內核態工作。系統調用結束後,又要切換回用戶態。這種環境的切換會消耗掉許多時間。庫函數訪問文件的時候根據需要,設置不同類型的緩衝區,從而減少了直接調用IO系統調用的次數,提高了訪問效率。

        例:應用程序調用printf函數時,函數執行的過程:

        用戶調用printf()函數     ===>     printf()函數想緩衝區寫數據,緩衝區的大小在標準庫中定義,有些系統是4096個字節     ===>     僅當1緩衝區已滿2沖洗緩衝區或者3遇到scanf()類輸入流時(讀者不理解的話先記住緩衝區已滿這個條件就足夠了其他的情況會在後續有介紹),會將緩衝區中數據輸出到屏幕。

        4. 標準庫I/O函數

        無論是編寫系統程序還是應用程序,都離不開I/O這個重要的環節。相對於低級的I/O操作(即系統調用級的I/O),標準I/O庫函數處理了很多細節,如緩存分配等,考慮到代碼的可移植性,開發人員應該在編寫代碼時儘可能使用標準庫函數。

        I/O的管理分類:由ANSI標準提供的標準IO庫函數幾乎被所有的操作系統支持,如windows下編寫的程序幾乎不用做任何修改就可以在linux下重新編譯運行,如:fopen、fread、fwrite、fclose。以系統調用的方式給用戶提供函數接口(遵循POSIX標準),例如linux操作系統提供的文件IO接口,如:open、close、read、write、ioctl,系統調用與操作系統直接相關,直接使用系統調用編寫的程序的可移植性差。

        頭文件<stdio.h>中聲明瞭標準C庫的I/O函數,標準C庫的I/O函數在所有通用計算機上的C語言實現都是相同的。使用標準C庫的I/O函數,打開或創建一個文件時,會返回一個指向FILE結構體的指針,該結構體包含了I/O函數爲管理文件所需要的儘可能多的信息,包括了用於I/O文件的文件描述符、指向流緩存的指針、緩存長度等。(定義路徑:/usr/include/libio.h,別名(typedef):/usr/include/stdio.h)

        關於庫的I/O函數我在C語言博文中已有詳細介紹,在此不再重述fopen、fclose、fread等函數。但作爲一個想提高自己編程水平的程序員,你還是應該熟練掌握格式化輸入輸出函數的編程

        △格式化輸入

        int scanf(const char *format, …) ==> 從標準輸入讀入信息
        int fscanf(FILE *stream,const char *format, …) ==> 從stream指向的文件中讀入信息
        int sscanf(const char *buf,const char *format, …) ==> 從buf指定的內存區域中讀入信息

        格式化輸入(例)

        1、取指定寬度的字符串
        char buf[512] = "";
        sscanf("123456", "%3s", buf);
        printf("%s\n", buf);
        結果爲:123
        2、僅保留字符串"hello world"中的world
        sscanf("hello world", "%*s%s", buf); 
        printf("%s\n", buf);

        結果爲:world

        3、取僅包含指定字符的字符串
        sscanf("bAcd", "%[Abcf]", buf);
        printf("%s\n", buf);
        結果爲:bAc
        4、取僅包含指定字符集的字符串
        ssscanf("123abcABC", "%[1-9a-z]", buf);
        printf("%s\n", buf);
        結果爲:123abc

        5、取到指定字符爲止的字符串
        sscanf("123456Aabcdedf", "%[^A]", buf);
        printf("%s\n", buf);
        結果爲:123456
        6、取到指定字符集爲止的字符串
        sscanf("123abcABC", "%[^A-Z]", buf);
        printf("%s\n", buf);
        結果爲:123abc

        7、取兩個字符之間的字符串
        sscanf("abc#def@ghi", "%*[^#]#%[^@]", buf);
        printf("%s\n", buf);
        結果爲:def
        8、分隔字符串2012:08:18 -2012:08:18
        char buf1[100] = "", buf2[100] = "";
        sscanf(“2010:08:18 - 2012:08:18”, "%s -%s", buf1, buf2); 
        sscanf(“2010:08:18 - 2012:08:18”,"%[0-9:] -%[0-9:]", buf1, buf2);

        格式化輸出:比較簡單,暫不舉例
        int printf(const char *format, …);  ==>  
輸出到標準輸出
        int fprintf(FILE *stream,const char *format, …);  ==>  
輸出到文件
        int sprintf(char *buf, const char *format, …);  ==>  
輸出到buf指定的內存區域
        int snprintf(char *buf, size_t n, const char *format, …);   ==>  輸出n個字節到buf指定的內存區域,注意:sprintf函數沒有指定寫入的字符數,可能會造成由buf指向的內存區域溢出。

        格式化輸入輸出小demo:實現IP地址(字符串)和整型數據(4個int型數據)之間的轉換,轉換後:p1=192; p2=168; p3=220; p4=5,分別實現:(1)IP->整型 (2)整型->IP。

        char *host=“192.168.220.5”;
        char ipaddr[16];
        int p1, p2, p3, p4;
        scanf(host, "%d.%d.%d.%d", &p1, &p2, &p3, &p4);
        sprintf(ipaddr, "%d.%d.%d.%d", p1, p2, p3, p4);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章