1. 程式人生 > 其它 >在windows 上呼叫so_學習筆記:系統呼叫

在windows 上呼叫so_學習筆記:系統呼叫

技術標籤:在windows 上呼叫so

系統呼叫(system call)是作業系統為使用者提供的服務介面。當應用程式或系統程式需要作業系統完成某項功能服務時,可通過系統呼叫將其需要的請求傳至給核心,核心呼叫相應的核心函式完成所需的處理,並將處理結果返回給應用程式或系統程式。如果沒有系統呼叫,使用者將不可能編寫出功能強大的應用程式或系統程式。從這個角度可以認為核心的主體是系統呼叫的集合,它是一組特殊的公共子程式。系統呼叫通常是由C和C++編寫的例程,但某些低階任務(例如,必須直接訪問硬體的任務)可能使用匯編語言指令編寫。

一個程式在執行時會執行大量的系統呼叫,下面舉一個簡單的例子來說明。

例:讀一個文字檔案,將其輸出,其C語言程式碼如下。

/* 檔名:disp.c*/#include #include int main(){  FILE *fp;  char c;  if ((fp=fopen("abc.txt","r"))==NULL) {    printf("File does not exist!\n");    exit(0);  }  while ((c=fgetc(fp))!=EOF) {    printf("%c",c);  }  fclose(fp);  return 0;}

該程式讀當前目錄上的檔案abc.txt,將其內容輸出。假設abc.txt的內容為:abcdef,編譯該程式,然後檢視該程式執行時會執行哪些系統呼叫。

07950b0f12c208e9a850b2a61bf6f3a2.png

使用strace命令可以檢視disp在執行時執行了哪些系統呼叫,在上圖的命名中,-o disp.log選項可以將程式在執行過程中執行的系統呼叫記錄在disp.log檔案中。disp.log檔案內容如下。

execve("./disp", ["./disp"], 0x7ffe09e1fde0 /* 63 vars */) = 0brk(NULL)                               = 0x55e5c0c32000arch_prctl(0x3001 /* ARCH_??? */, 0x7ffdbb49fbc0) = -1 EINVAL (Invalid argument)access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=86026, ...}) = 0mmap(NULL, 86026, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f193d1bb000close(3)                                = 0openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832pread64(3, "\6\0\0\0\4\0\0\
[email protected]
\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\0"..., 784, 64) = 784pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\363\377?\332\200\270\27\304d\245n\355Y\377\t\334"..., 68, 880) = 68fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f193d1b9000pread64(3, "\6\0\0\0\4\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\0"..., 784, 64) = 784pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\363\377?\332\200\270\27\304d\245n\355Y\377\t\334"..., 68, 880) = 68mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f193cfc7000mprotect(0x7f193cfec000, 1847296, PROT_NONE) = 0mmap(0x7f193cfec000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f193cfec000mmap(0x7f193d164000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f193d164000mmap(0x7f193d1af000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f193d1af000mmap(0x7f193d1b5000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f193d1b5000close(3) = 0arch_prctl(ARCH_SET_FS, 0x7f193d1ba540) = 0mprotect(0x7f193d1af000, 12288, PROT_READ) = 0mprotect(0x55e5becfd000, 4096, PROT_READ) = 0mprotect(0x7f193d1fe000, 4096, PROT_READ) = 0munmap(0x7f193d1bb000, 86026) = 0brk(NULL) = 0x55e5c0c32000brk(0x55e5c0c53000) = 0x55e5c0c53000openat(AT_FDCWD, "abc.txt", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0664, st_size=7, ...}) = 0read(3, "abcdef\n", 4096) = 7fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0write(1, "abcdef\n", 7) = 7read(3, "", 4096) = 0close(3) = 0exit_group(0) = ?+++exitedwith0+++

檢視disp.log檔案,可以發現從disp開始執行到結束,總共呼叫了40次系統呼叫。

通常,系統每秒執行數千次系統呼叫。然而,大多數程式設計師不會關心這些細節。通常,應用程式開發人員根據應用程式程式設計介面(Application Programming Interface,API)設計程式。API指定了程式設計師可以使用的一組函式,包括傳遞給每個函式的引數和程式設計師可以期望的返回值。比如上例中的fopen、printf、exit、fgetc、fclose等等。程式設計師可以使用的三種最常見的API是Windows系統的Windows API、基於POSIX系統的POSIX API(其中包括幾乎所有版本的UNIX、Linux和Mac OSX)以及在Java虛擬機器上執行的Java API。程式設計師通過作業系統提供的程式碼庫訪問API。對於用C語言編寫的UNIX和Linux程式,該庫稱為libc。每個作業系統對每個系統呼叫都有自己的名稱。

在後臺,API函式通常為程式設計師呼叫實際的系統呼叫。例如,Windows函式CreateProcess()(用於建立新程序)實際上呼叫了Windows核心中的NTCreateProcess()系統呼叫。

為什麼程式設計師更喜歡使用API而非系統呼叫進行程式設計呢?這樣做是有原因的。一是程式的可移植性。程式設計師使用API設計程式可以期望自己的程式在支援相同API的任何系統上編譯和執行(儘管在現實中,體系結構的差異往往使這一點看起來很困難)。二是實際的系統呼叫通常比API更注重細節且更加難用。然而,API中的函式與其核心中相關聯的系統呼叫之間還是存在著緊密的聯絡。事實上,許多POSIX和Windows的API類似於UNIX、Linux和Windows作業系統提供的系統呼叫。

標準API例子--Unix/Linux系統中的read()函式。通過在命令列上輸入如下命令得到這個函式的幫助資訊: man read 該API描述如下:

#include

ssize_t read(intfd, void *buf, size_t count);

呼叫函式read()的程式應該包括標頭檔案unistd.h,這是該檔案定義了資料型別ssize_t和size_t(還有其他許多型別)。read()傳入引數如下:

int fd:要讀的檔案描述符。

void *buf:所讀資料存放位置。

size_t count:要讀的最大位元組數。

當成功讀入後,會返回讀取的資料的位元組數。返回值為0表示檔案結束。如果出錯,會返回-1。

參考文獻: [1] Silberschatz A . 作業系統概念(原書第9版)[M]. 鄭扣根等譯. 高等教育出版社, 2018. [2]https://www.cnblogs.com/sun-frederick/p/4763306.html 附錄 linux系統——fread()與read() 函式族 區別 fread與read區別: 1. fread是帶緩衝的,read不帶緩衝。 2. fread是標準C裡定義的, read是POSIX中定義的。 3. fread可以讀一個結構,read在linux/unix中讀二進位制與普通檔案沒有區別。 4. fopen不能指定要建立檔案的許可權,open可以指定許可權。 5. fopen返回指標, open返回檔案描述符(整數)。 6. Linux/Unix中任何裝置都是檔案,都可以用open,read。 如果檔案的大小是8k。你如果用read/write,且只分配了2k的快取,則要將此檔案讀出需要做4次系統呼叫來實際從磁碟上讀出。如果你用fread/fwrite,則系統自動分配快取,則讀出此檔案只要一次系統呼叫從磁碟上讀出。 也 就是用read/w rite要讀4次磁碟,而用fread/fwrite則要讀1次磁碟。 效率比read/write要高4倍。 如果程式對記憶體有限制,則用read/write比較好。用fread和fwrite會自動分配快取,速度會很快,比自己來做要簡單。如果要處理一些特殊的描述符,用read 和write,如套介面、管道之類的。 系統呼叫write的效率取決於你buf的大小和你要寫入的總數量,如果buf太小,你進入核心空間的次數大增,效率就低下。而fwrite會替你做快取,減少了實際出現的系統呼叫,所以效率比較高。 如果只調用一次(可能嗎?),這倆差不多,嚴格來說write要快一點點(因為實際上fwrite最後還是用了write做真正的寫入檔案系統工作),但是這其中的差別無所謂。