今天我們來看一下幾個系統調用函數,以及他們的基本用法。
一、open
int open ( char *filename , int flags , mode_t mode );
open 函數将filename轉換為一個檔案描述符,并且傳回描述符數字。傳回的描述符總是在程序中目前沒有打開的最小描述符。
①filename
檔案名
②flags
O_RDONLY | 隻讀 |
O_WRONLY | 隻寫 |
O_RDWR | 可讀可寫 |
O_CREAT | 如果檔案不存在,就建立它的一個空檔案 |
O_TRUNC | 如果檔案已經存在,就清空它 |
O_APPEND | 在每次寫操作前,設定檔案位置到檔案的結尾處,也就是追加操作 |
flags參數也可以是一個或者更多位掩碼的或,為寫提供給一些額外的提示
③mode
mode參數制定了新檔案的通路權限位
掩碼 | 描述 |
S_IRUSR S_IWUSR S_IXUSR | 使用者(擁有者)能夠讀這個檔案 使用者(擁有者)能夠寫這個檔案 使用者(擁有者)能夠執行這個檔案 |
S_IRGRP S_IWGRP S_IXGRP | 擁有者所在組的成員能夠讀這個檔案 擁有者所在組的成員能夠寫這個檔案 擁有者所在組的成員能夠執行這個檔案 |
S_IROTH S_IWOTH S_IXOTH | 其他人(任何人)能夠讀這個檔案 其他人(任何人)能夠讀寫這個檔案 其他人(任何人)能夠執行這個檔案 |
下面的函數說明如何以讀的方式打開一個已存在的檔案
int fd=open("foo.txt",O_RDONLY,0);
下面的函數說明如何打開一個已存在的檔案,并在後面添加一些資料
int fd=open("foo.txt",O_WRONLY|O_APPEND,0);
下面的函數經常用作建立一個新檔案
int fd=open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,0);
二、read、write
ssize_t read ( int fd , void *buf , size_t n );
read函數從描述符為fd的目前檔案位置複制最多n個位元組到記憶體位置buf。傳回值-1表示一個錯誤,而傳回值0表示EOF。否則,傳回值表示的實際傳送的位元組數量。
ssize_t write ( int fd, const void *buf , size_t n );
write函數從記憶體位置buf複制最多n個位元組到描述符fd的目前檔案位置。
下面的程式使用read和write調用一次一個位元組的從标準輸入(鍵盤)複制到标準輸出(螢幕)。
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char c;
while(read(STDIN_FILENO,&c,1)!=0)
write(STDOUT_FILENO,&c,1);
exit(0);
}
敲一個字元,按下回車就顯示
三、lseek
off_t lseek ( int fd , off_t offset , int whence );
lseek 定位一個檔案,傳回目前光标距檔案開頭的位元組數
offset | 偏移量 |
SEEK_SET | 參數offset 即為新的讀寫位置. |
SEEK_CUR | 以目前的讀寫位置往後增加offset 個位移量. |
SEEK_END | 将讀寫位置指向檔案尾後再增加offset 個位移量. 當whence 值為SEEK_CUR 或SEEK_END 時, 參數offet 允許負值的出現. |
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
/**
abc.text檔案裡存放一個字元串“abcdefghijklmn”
*/
int fd=open("abc.text",O_RDONLY,0);
int buf[20];
int num[10];
int buf_size=read(fd,buf,20);
printf("buf_size=%d",buf_size);
//把光标設定到偏移量為2的地方
int current_position=lseek(fd,2,SEEK_SET);
printf("current_position=%d",current_position);
//将光标後移3位
int new_position_1=lseek(fd,3,SEEK_CUR);
printf("new_position_1=%d",new_position_1);
//讀檔案3個位元組,輸出到螢幕上
read(fd,num,new_position_1-current_position);
write(STDOUT_FILENO,num,new_position-current_position);
printf("\n");
//以檔案末尾為基準,向前移1位
int new_position_2 =lseek(fd,-1,SEEK_END);
pritnf("new_position_2=%d",new_position_2);
//把光标置于檔案末尾,用這種辦辦法也可以算作求出檔案大小
int end_position=lseek(fd,0,SEEK_END);
printf("end_position=%d",end_position);
}
來看一下運作結果,讀位元組的時候,可以發現檔案的光标真的有在移動。
四、stat
應用程式能夠通過調用stat函數,檢索到關于檔案的資訊,有時也稱為檔案的中繼資料
int stat (const char *filename , struct *buf );
結構體buf有13個成員,我們簡單說兩個常用的即可。st_size,表示檔案位元組數大小。st_mode成員則編碼了檔案通路許可位和通路類型。Linux再sys/stat.h中定義了宏謂詞來确定st_mode成員的檔案類型,
S_ISREG(m) | 這是一個普通檔案麼? |
S_ISDIR(m) | 這是一個目錄檔案麼 |
S_ISSOCK(m) | 這是一個網絡套接字麼? |
接下來看一個小代碼,如何利用st_mode檢視一個檔案權限和類型
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
struct stat s;
char *readok,*writeok,*exeok;
stat(argv[1],&s);
if(S_ISREG(s.st_mode))//普通檔案
type="regular";
else if(S_ISDIR(s.st_mode))//目錄
type="directionary";
else type="others";
//檢視讀權限
if((s.st_mode&S_IRUSR))
readok="yes";
else readok="no";
//檢視寫權限
if((s.st_mode&S_IWUSR))
writeok="yes";
else writeok="no";
//檢視執行權限
if((s.st_mode&S_IXUSR))
exeok="yes";
else exeok="no";
printf("type\treadok\twriteok\texeok\n");
printf("%s\t%s\t\%s\t%s\n",type,readok,writeok,exeok);
}
五、共享檔案
可以用許多不同的方式來共享Linux。核心用三個相關的樹結構來表示打開的檔案。
描述符表 | 每個程序都有它獨立的描述符表,它的表項是由結成打開的檔案描述符來索引的。每個程序都有它獨立的描述符表項指向檔案表中的一個表項 |
檔案表 | 打開檔案的集合是由一張檔案表來表示的,所有程序共享這張表 |
v-node表 | 同檔案表一樣,所有的程序共享這張v-node表。每個表項包含stat結構中的大多數資訊,包括st_mode和st_size成員。 |
①下圖展示了一個示例,其中描述符1和3通過不同給的打開檔案表表項來引用兩個不同的檔案。這是一個典型的情況,沒有共享檔案,并且每個描述符對應一個不同的檔案。
大緻的代碼
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("123.text",O_RDONLY,0);
② 多個描述符也可以通過不同的檔案表表項來引用同一個檔案。例如,如果同一個filename調用open函數兩次,就會發生這種情況。關鍵思想是每個描述符都有它自己的檔案位置,是以對不同描述符的讀操作可以從檔案的不同位置擷取資料。
大緻的代碼
int fd1=open("abc.text",O_RDONLY,0);
int fd3=open("abc.text",O_RDONLY,0);
③父子程序共享相同的檔案表集合,是以共享相同的檔案位置。一個最重要的結果就是,在核心删除相應檔案表表項之前,父子程序必須都關閉了他們的描述符。
一些小練習
①
//假設123.text檔案中存放的内容是123456790
int fd1=open("abc.text",O_RDONLY,0);
int fd2=open("123.text",O_RDONLY,0);
int buf1[20],buf2[20];
read(fd1,buf1,20);
write(STDOUT_FILENO,buf1,20);
read(fd2,buf2,20);
write(STDOUT_FILENO,buf2,20);
他的結果是簡單的。對應了第一種情況,打開了兩個不同的檔案,都是從頭輸出.。
②
//假設我們讀的檔案中的内容是 abcdefgh
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char c1, c2, c3;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
fd2 = Open(fname, O_RDONLY, 0);
fd3 = Open(fname, O_RDONLY, 0);
//此處解釋以下,dup2,簡單來說這個函數的效果就是凡是對fd3的操作都可以看做是對fd2的操作
dup2(fd2, fd3);
Read(fd1, &c1, 1);
Read(fd2, &c2, 1);
Read(fd3, &c3, 1);
printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
由于dup2,對fd3的操作都相當于對f2的操作。c1讀到fd1中的a,c2讀到了fd2中的a,此時fd2中的光标在‘a’的後面,是以c3讀到了fd2中的b。
③
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
//建立一個名為fname的新檔案,可讀可寫,在裡面寫入pqrs
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, "pqrs", 4);
//以追加寫的方式打開剛剛建立号的fname的檔案,在裡面寫入jklmn
fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
Write(fd3, "jklmn", 5);
//dup,使之後對fd2的操作都變為對fd1的操作
fd2 = dup(fd1); /* Allocates new descriptor */
Write(fd2, "wxyz", 4);
Write(fd3, "ef", 2);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
最終它的運作結果是 pqrswxyznef ,我們來看一下他的實作過程。
④
//假設讀取的檔案中存放的是abcdefgh
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
printf("s=%d\n",s);
char c1, c2;
char *fname = argv[1];
fd1 = open(fname, O_RDONLY, 0);
read(fd1, &c1, 1);
if (fork()) {
/* Parent */
sleep(s);
read(fd1, &c2, 1);
printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
read(fd1, &c2, 1);
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
可以看到,子程序列印結束後,大概等待一秒,父程序才列印出來。,read在fork之前進行了第一次讀,c1沒有争議的讀到了a,此時光标在‘a’後。子程序因為沒有sleep,進而先比父程序先讀,讀到了b,此時光标在‘b’後。父程序sleep結束後,讀取c2,也就讀到了c。