系統(tǒng)調(diào)用
- 所謂系統(tǒng)調(diào)用是指操作系統(tǒng)提供給用戶程序調(diào)用的一組“特殊”接口,用戶程序可以通過這組“特殊”接口來獲得操作系統(tǒng)內(nèi)核提供的服務(wù)。
- 例如用戶可以通過進程控制相關(guān)的系統(tǒng)調(diào)用來創(chuàng)建進程、實現(xiàn)進程調(diào)度、進程管理等。
- 為什么用戶程序不能直接訪問系統(tǒng)內(nèi)核提供的服務(wù)呢?
這是由于在Linux中,為了更好地保護內(nèi)核空間,將程序的運行空間分為內(nèi)核空間和用戶空間(也就是常稱的內(nèi)核態(tài)和用戶態(tài)),它們分別運行在不同的級別上,在邏輯上是相互隔離的。因此,用戶進程在通常情況下不允許訪問內(nèi)核數(shù)據(jù),也無法使用內(nèi)核函數(shù),它們只能在用戶空間操作用戶數(shù)據(jù),調(diào)用用戶空間的函數(shù)。 - 但是,在有些情況下,用戶空間的進程需要獲得一定的系統(tǒng)服務(wù)(調(diào)用內(nèi)核空間程序),這時操作系統(tǒng)就必須利用系統(tǒng)提供給用戶的“特殊接口”——系統(tǒng)調(diào)用規(guī)定用戶進程進入內(nèi)核空間的具體位置(方法:軟中斷+系統(tǒng)調(diào)用號)。進行系統(tǒng)調(diào)用時,程序運行空間需要從用戶空間進入內(nèi)核空間,處理完后再返回到用戶空間。
系統(tǒng)調(diào)用、用戶空間和內(nèi)核空間程序關(guān)系
1、Linux的虛擬地址空間為0~4G,Linux內(nèi)核將這4G字節(jié)的空間分為用戶空間和內(nèi)核空間兩部分。
2、內(nèi)核空間的虛擬地址范圍為0xC0000000到0xFFFFFFFF,該部分空間供內(nèi)核使用,存放內(nèi)核代碼及內(nèi)核數(shù)據(jù)。
3、用戶空間虛擬地址范圍為 0x00000000到0xBFFFFFFF,供用戶的各個進程使用,存放用戶代碼和用戶數(shù)據(jù)。
4、用戶進程無法訪問直接訪問內(nèi)核空間,必須通過API觸發(fā)內(nèi)核的軟中斷機制進行系統(tǒng)調(diào)用,以達到訪問內(nèi)核空間的目的。
API
- 前面講到的系統(tǒng)調(diào)用并不是直接與程序員進行交互的,它僅僅是一個通過軟中斷機制向內(nèi)核提交請求,以獲取內(nèi)核服務(wù)的接口。在實際使用中程序員調(diào)用的通常是用戶編程接口——API
-
系統(tǒng)命令相對API更高了一層,它實際上一個可執(zhí)行程序,它的內(nèi)部引用了用戶編程接口(API)來實現(xiàn)相應(yīng)的功能。
文件描述符
- 內(nèi)核如何區(qū)分和引用特定的文件呢?這里用到了一個重要的概念——文件描述符。
- 對于Linux而言,所有對設(shè)備和文件的操作都是使用文件描述符來進行的。
- 文件描述符是一個非負的整數(shù),它是一個索引值,并指向在內(nèi)核中每個進程打開文件的記錄表。
- 當(dāng)打開一個現(xiàn)存文件或創(chuàng)建一個新文件時,內(nèi)核就向進程返回一個文件描述符;當(dāng)需要讀寫文件時,也需要把文件描述符作為參數(shù)傳遞給相應(yīng)的函數(shù)。
- 通常,一個進程啟動時,都會打開3個文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)出錯處理。
這3個文件分別對應(yīng)文件描述符為0、1和2(也就是宏替換STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO)。
底層文件I/O操作
-
open()函數(shù)是用于打開或創(chuàng)建文件,在打開或創(chuàng)建文件時可以指定文件的屬性及用戶的權(quán)限等各種參數(shù)。
-
close()函數(shù)是用于關(guān)閉一個被打開的文件。當(dāng)一個進程終止時,所有被它打開的文件都由內(nèi)核自動關(guān)閉,很多程序都使用這一功能而不顯示地關(guān)閉一個文件。
-
read()函數(shù)是用于將從指定的文件描述符中讀出的數(shù)據(jù)放到緩存區(qū)中,并返回實際讀入的字節(jié)數(shù)。若返回0,則表示沒有數(shù)據(jù)可讀,即已達到文件尾。讀操作從文件的當(dāng)前指針位置開始。當(dāng)從終端設(shè)備文件中讀出數(shù)據(jù)時,通常一次最多讀一行。
-
write()函數(shù)是用于向打開的文件寫數(shù)據(jù),寫操作從文件的當(dāng)前指針位置開始。對磁盤文件進行寫操作,若磁盤已滿或超出該文件的長度,則write()函數(shù)返回失敗。
-
lseek()函數(shù)是用于在指定的文件描述符中將文件指針定位到相應(yīng)的位置。它只能用在可定位(可隨機訪問)文件操作中。管道、套接字和大部分字符設(shè)備文件是不可定位的,所以在這些文件的操作中無法使用lseek()調(diào)用。
舉一個復(fù)制文件的例子:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define BUFFER_SIZE 1024
#define SRC_FILE_NAME "srcfile.txt"
#define DEST_FILE_NAME "destfile.txt"
#define OFFSET 1024*10
int main()
{
int src_fd,dest_fd,real_read_long=0,rtn_num;
char buffer[BUFFER_SIZE];
long long int cpnum=0;
memset(buffer,0,BUFFER_SIZE);
//open file
src_fd=open(SRC_FILE_NAME,O_RDONLY);
dest_fd=open(DEST_FILE_NAME,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if(src_fd<0||dest_fd<0)
{
printf("open srcfile or destfile fail!\n");
exit(1);
}
//move srcfile data pointer
rtn_num=lseek(src_fd,-OFFSET,SEEK_END);
if(rtn_num==-1)
{
printf("move srcfile date pointer fail!\n");
return 1;
}
printf("current srcfile pointer is %d\n",rtn_num);
//copy srcfile data to destfile
while(real_read_long=read(src_fd,buffer,BUFFER_SIZE))
{
cpnum+=real_read_long;//count copy bytes
write(dest_fd,buffer,real_read_long);
}
printf("Copy finished!(size:%lld bytes)\n",cpnum);
close(dest_fd);
close(src_fd);
return 0;
}
文件鎖
- 舉例:
1) 進程“A”打開和讀取一個文件,此文件包含賬戶相關(guān)的一些信息。
2) 進程“B”也打開了這個文件,并讀取了文件中的信息。
3) 現(xiàn)在,進程“A”更改了其副本中的一條余額記錄,并將其寫入文件。
4) 此時,進程“B”并不知道上次讀取的文件已經(jīng)被更改,它還保存著原始的文件副本。然后,進程“B”更改了“A”操作的那條相同的記錄,并將記錄寫入文件。
5) 此時,文件中將只保存了進程“B”更改過的記錄。 - 為避免這種事情發(fā)生,就要用文件鎖來確保操作的“序列化”。
- 文件鎖包括建議性鎖和強制性鎖。
建議性鎖又叫協(xié)同鎖,它要求每個上鎖文件的進程都要檢查是否有鎖存在,并且尊重已有的鎖。在一般情況下,內(nèi)核和系統(tǒng)都不使用建議性鎖。
強制性鎖是由內(nèi)核執(zhí)行的鎖,當(dāng)一個文件被上鎖進行寫入操作的時候,內(nèi)核將阻止其他任何文件對其進行讀寫操作。采用強制性鎖對性能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。 - 在Linux中,實現(xiàn)文件上鎖的函數(shù)有l(wèi)ockf()和fcntl(),其中l(wèi)ockf()用于對文件施加建議性鎖,而fcntl()不僅可以施加建議性鎖,還可以施加強制鎖。同時,fcntl()還能對文件的某一記錄上鎖,也就是記錄鎖。
記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個進程都能在文件的同一部分建立讀取鎖。而寫入鎖又稱為排斥鎖,在任何時刻只能有一個進程在文件的某個部分上建立寫入鎖。當(dāng)然,在文件的同一部分不能同時建立讀取鎖和寫入鎖。 -
fcntl()函數(shù)格式(1)
- fcntl()函數(shù)格式(2)- flock結(jié)構(gòu)
struct flock
{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}

文件鎖例子:
lock_set.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
void lock_set(int fd,short type)
{
struct flock lock;
lock.l_type=type;
lock.l_start=0;
lock.l_whence=SEEK_SET;
lock.l_len=0;
lock.l_pid=-1;
fcntl(fd,F_GETLK,&lock);
if(lock.l_type!=F_UNLCK)
{
if(lock.l_type==F_RDLCK)
printf("The file %d has already F_RDLCK by process %d\n",fd,lock.l_pid);
if(lock.l_type==F_WRLCK)
printf("The file %d has already F_WRLCK by process %d\n",fd,lock.l_pid);
}
lock.l_type=type;
if(fcntl(fd,F_SETLKW,&lock))
printf("The file %d was faile to be set lock by process %d\n",fd,getpid());
switch(lock.l_type)
{
case F_RDLCK:
printf("The file %d was set readlock by process %d\n",fd,getpid());
break;
case F_WRLCK:
printf("The file %d was set writelock by process %d\n",fd,getpid());
break;
case F_UNLCK:
printf("The file %d was released lock by process %d\n",fd,getpid());
break;
default:
break;
}
}
main.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/file.h>
#include<stdio.h>
#include<stdlib.h>
#include"lock_set.h"
int main()
{
int fd;
fd=open("hello",O_RDWR|O_CREAT);
if(fd<0)
{
printf("open file hello fail!\n");
exit(1);
}
lock_set(fd,F_WRLCK);
write(fd,"Hello,world!",12);
lock_set(fd,F_UNLCK);
close(fd);
return 0;
}
I/O處理的模型
- 阻塞I/O模型:在這種模型下,若所調(diào)用的I/O函數(shù)沒有完成相關(guān)的功能,則會使進程掛起,直到相關(guān)數(shù)據(jù)到達才會返回。如常見對管道設(shè)備、終端設(shè)備和網(wǎng)絡(luò)設(shè)備進行讀寫時經(jīng)常會出現(xiàn)這種情況。
- 非阻塞模型:在這種模型下,當(dāng)請求的I/O操作不能完成時,則不讓進程睡眠,而且立即返回。非阻塞I/O使用戶可以調(diào)用不會阻塞的I/O操作,如open()、write()和read()。如果該操作不能完成,則會立即返回出錯(例如:打不開文件)或者返回0(例如:在緩沖區(qū)中沒有數(shù)據(jù)可以讀取或者沒空間可以寫入數(shù)據(jù))。






