系統呼叫與標準IO庫區別
Linux下對檔案操作有兩種方式:系統呼叫(system call)和庫函式呼叫(Library functions)。系統呼叫實際上就是指最底層的一個呼叫,在linux程式設計裡面就是底層呼叫的意思。面向的是硬體。而庫函式呼叫則面向的是應用開發的,相當於應用程式的api,採用這樣的方式有很多種原因,第一:雙緩衝技術的實現。第二,可移植性。第三,底層呼叫本身的一些效能方面的缺陷。第四:讓api也可以有了級別和專門的工作面向。
1、系統呼叫(底層檔案訪問)
系統呼叫提供的函式如open, close, read, write, ioctl等
系統呼叫通常用於底層檔案訪問(low-level file access),例如在驅動程式中對裝置檔案的直接訪問。
系統呼叫是作業系統相關的,因此一般沒有跨作業系統的可移植性。
2、庫函式呼叫(標準IO庫)
標準IO庫函式提供的檔案操作函式如fopen, fread, fwrite, fclose, fflush, fseek等,
庫函式呼叫通常用於應用程式中對一般檔案的訪問。
庫函式呼叫是系統無關的,因此可移植性好。
由於庫函式呼叫是基於C庫的,因此也就不可能用於核心空間的驅動程式中對裝置的操作。
系統呼叫發生在核心空間,因此如果在使用者空間的一般應用程式中使用系統呼叫來進行檔案操作,會有使用者空間到核心空間切換的開銷。事實上,即使在使用者空間使用庫函式來對檔案進行操作,因為檔案總是存在於儲存介質上,因此不管是讀寫操作,都是對硬體(儲存器)的操作,都必然會引起系統呼叫。也就是說,庫函式對檔案的操作實際上是通過系統呼叫來實現的。例如C庫函式fwrite()就是通過write()系統呼叫來實現的。
使用庫函式也有系統呼叫的開銷,為什麼不直接使用系統呼叫呢?這是因為,讀寫檔案通常是大量的資料(這種大量是相對於底層驅動的系統呼叫所實現的資料操作單位而言),這時,使用庫函式就可以大大減少系統呼叫的次數。這一結果又緣於緩衝區技術。在使用者空間和核心空間,對檔案操作都使用了緩衝區,例如用fwrite寫檔案,都是先將內容寫到使用者空間緩衝區,當用戶空間緩衝區滿或者寫操作結束時,才將使用者緩衝區的內容寫到核心緩衝區,同樣的道理,當核心緩衝區滿或寫結束時才將核心緩衝區內容寫到檔案對應的硬體媒介。
程式碼測試:
一、系統呼叫例子:
#include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> int main(){ int ret = 0; int nCount = 0; int offset = 0; int size = 0; char *filename = "writefile"; char buffer[20]; size = sizeof(buffer); memset(buffer, '-', size); buffer[size-1] = '\0'; int fd = open(filename, O_RDWR | O_CREAT, 0666); if (fd < 0){ printf("open file failed\n"); return; } while(offset < size){ ret = write(fd, buffer + offset, (size - offset) > 4 ? 4 : (size - offset) ); if(ret <= 0){ printf("write file failed\n"); break; } offset += ret; } close(fd); }
strace跟蹤呼叫過程:
execve("./sysio", ["./sysio"], [/* 23 vars */]) = 0
... ...
open("writefile", O_RDWR|O_CREAT, 0666) = 3
write(3, "----", 4) = 4
write(3, "----", 4) = 4
write(3, "----", 4) = 4
write(3, "----", 4) = 4
write(3, "---\0", 4) = 4
close(3) = 0
exit_group(0) = ?
-----------------------------------------
呼叫五次write
time檢視執行時間:
real 0m0.005s
user 0m0.000s
sys 0m0.003s
二、庫函式呼叫例子:
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main(){
int ret = 0;
int nCount = 0;
int offset = 0;
int size = 0;
int writesize = 0;
char *filename = "fwritefile";
char buffer[20];
size = sizeof(buffer);
memset(buffer, '-', size);
buffer[size-1] = '\0';
FILE *pfile = fopen(filename, "wr+");
if (pfile == NULL){
printf("fopen file failed\n");
return;
}
while(offset < size){
writesize = (size - offset) > 4 ? 4 : (size - offset);
ret = fwrite(buffer + offset, writesize, 1, pfile );
if(ret <= 0){
printf("fwrite file failed\n");
break;
}
offset += writesize;
}
fclose(pfile);
}
strace跟蹤呼叫過程:
execve("./sysio", ["./sysio"], [/* 23 vars */]) = 0
brk(0) = 0x21e1000
... ...
open("fwritefile", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fca5f25b000
write(3, "-------------------\0", 20) = 20
close(3) = 0
munmap(0x7fca5f25b000, 4096) = 0
exit_group(0)
-------------------------------------
只調用一次write
time檢視執行時間:
real 0m0.003s
user 0m0.002s
sys 0m0.001s
把buffer改成200000測試呼叫時間則有明顯無別
系統呼叫(write)耗時:
real 0m0.078s
user 0m0.003s
sys 0m0.072s //核心時間大大增加
庫函式呼叫(fwrite)耗時:
real 0m0.008s
user 0m0.007s
sys 0m0.001s //無變化