Unix I/O
一個Linux檔案就是一個m個位元組的序列,所有的I/O裝置(例如網絡、磁盤和終端)都被模式化為檔案,而所有的輸入和輸出都被當做對此項對應的檔案的讀和寫來進行執行。這種裝置優雅的映射為檔案的方式,允許Linux核心引出一個簡單的、低級的應用接口,稱為Unix I/O,這使得所有的輸入和輸出都能以一種統一且一緻的方式來執行:
- 打開檔案。應用程式要求核心打開相應的檔案,一次通路I/O裝置。核心傳回一個很小的非負整數,叫做描述符(fd),他在後續對此檔案的所有操作中辨別這個檔案。核心記錄有關這個打開檔案的所有資訊。應用程式隻需記住這個辨別符。
- shell建立的每個程序都需要打開三個檔案:标準輸入(fd=0)、标準輸出(fd=1)、标準錯誤(fd=2)。
- 改變目前的檔案位置。
- 讀寫檔案。
- 關閉檔案。
打開和關閉檔案
打開(open)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open (char *filename,int flags,mode_t mode);
傳回:若成功則為新檔案描述符,若出錯為-1。
傳回的新檔案描述符總是在程序中目前沒有打開的最小的描述符。
flags參數指明了程序通路檔案的方式:
- O_RDONLY:隻讀。
- O_WRONLY:隻寫。
- O_RDWR:可讀可寫。
- O_CREAT:如果檔案不存在,就建立它的一個截斷的(空)檔案。
- O_TRUNC:如果檔案已經存在,就截斷它。
-
O_APPEND:在每次寫操作前,設定檔案位置到檔案結尾處。
以上是常用的通路方式,并非全部。
mode參數指定了新檔案的通路權限位:
- S_IRUSR:使用者(擁有者)能夠讀這個檔案。
- S_IWUSR:使用者(擁有者)能夠寫這個檔案。
- S_IXUSR:使用者(擁有者)能夠執行這個檔案。
- S_IRGRP:擁有者所在組的成員能夠讀這個檔案。
- S_IWGRP:擁有者所在組的成員能夠寫這個檔案。
- S_IXGRP:擁有者所在組的成員能夠執行這個檔案。
- S_IROTH:其他者(任何者)能夠讀這個檔案。
- S_IWOTH:其他者(任何者)能夠寫這個檔案。
- S_IXOTH:其他者(任何者)能夠執行這個檔案。
關閉
#include <unistd.h>
int close(int fd);
傳回:若成功則為0,若出錯則為-1.
打開檔案使用完後就要用close函數進行關閉,close函數會釋放fd指向的檔案,并釋放該檔案所占用的檔案描述符。
讀和寫檔案
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
傳回:若成功則為度的位元組數,若EOF則為0,若出錯則為-1。
ssize_t write(int fd,const void *buf,size_t n);
犯規:若成功則為寫的位元組數,若出錯則為-1。
read函數從描述符為fd的目前檔案位置複制最多n個位元組到記憶體位置buf。傳回值-1表示一個錯誤,而傳回值0表示EOF。否則,傳回值表示的是實際傳送的位元組數量。
write函數從記憶體位置buf複制至多n個位元組到描述符fd的目前檔案位置。
ssize_t和size_t的差別在于,ssize_t是有符号的長整型,size_t是為無符号的長整型。
定位
lseek函數
詳見:
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結和本聲明。
本文連結:https://blog.csdn.net/weixin_42031299/article/details/90723673
讀取檔案中繼資料
應用程式能夠通過調用stat和fstat函數,檢索到關于檔案的資訊(有時也稱為檔案的中繼資料)
函數解析詳見:
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結和本聲明。
本文連結:https://blog.csdn.net/codeTZ/article/details/52760382
共享檔案
核心用三個相關的資料結構表示打開的檔案:
- 描述符表。每個程序都有它獨立的描述符表,他的表象是有程序打開的檔案描述符來索引的。每個打開的描述符表項指向檔案表中的一個表項。
- 檔案表。打開檔案的集合是有一張檔案表來表示的,所有的程序共享這張表。每個檔案表的表項組成(針對我們的目的)包括目前的檔案位置、引用計數(即目前指向該表項的描述符表項數),以及一個指向v-node表中相應表項的指針。
-
v-node表。同檔案表一樣,所有的程序共享這張v-node表。每個表項包含stat結構中的大多數資訊。
多個描述符可以打開不同的檔案,這是一個典型的情況,無共享檔案。
多個描述符也可以通過不同檔案表項來引用同一個檔案。例如,如果以同一個filename調用open函數兩次,就會發生這種情況。
父子程序也可以共享檔案,假設在fork之前父程序有打開的檔案。fork後子程序就有一個父程序描述符表的副本。父子程序共享相同的打開檔案表集合,是以共享相同的檔案位置。一個更重要的結果就是,在核心删除相應檔案表表項之前,父子程序必須都關閉了他們的描述符。
I/O重定向
dup函數
#include <unistd.h>
int dup2(int oldfd,int newfd);
傳回:若成功則為非負的描述符,若出錯則為-1
dup2函數複制描述符表項oldfd到描述符表項newfd,覆寫描述符表表項newfd以前的内容。如果newfd已經打開了,dup2會複制oldfd之前關閉newfd。
習題
目标檔案abcde.txt
一.ffiles1.c
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(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;
}
結果:
分析:
fd1、fd2、fd3三個檔案描述符打開檔案abcde.txt共享v-node表,但三個描述符指向的打開檔案表不相同,分别為檔案、檔案2、檔案3。
使用dup2函數将fd2複制到fd3,使得兩個檔案描述符現在都指向檔案2,是以兩個檔案描述符指向的檔案位置相同,讀了fd1指向的檔案1中的c1=a了之後,讀fd2和fd3指向的檔案2,因為是兩個不同的檔案,不會互相影響,是以檔案2從頭開始讀,是以c2=a,然後fd2會後移,fd3和fd2位置相同是以c3=b。
二.ffiles2.c
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
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;
}
結果:
分析:
前面有講到父子程序是共享檔案位置的,也就不難了解為何子程序c2=b,父程序c2=c了。注(因為sleep函數傳參的不同使得父子程序執行順序有随機性)
三.ffiles3.c
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
fd1 = open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
write(fd1, "pqrs", 4);
fd3 = open(fname, O_APPEND|O_WRONLY, 0);
write(fd3, "jklmn", 5);
fd2 = dup(fd1); /* Allocates new descriptor */
write(fd2, "wxyz", 4);
write(fd3, "ef", 2);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
結果:
分析:
fd1以O_CREAT|O_TRUNC|O_RDWR建立并打開了一個可讀可寫的檔案,如果檔案已存在則将其内容清空;fd3則是可以在檔案末尾對檔案内容進行添加,以隻寫的方式打開檔案。
起初檔案中内容為abcde,但是fd1打開它的時候就清空了然後往檔案裡面寫資料pqrs。
fd3打開檔案,在後面加上jklmn,此時檔案中内容為pqrsjklmn,fd1的位置上在j處,fd3位置在檔案末尾。
用dup函數,讓fd2成為fd1的副本,是以fd2的位置也在j處,從j處開始寫入資料wxyz,将jklm覆寫,留下一個n,最後再在檔案末尾添加ef,得到結果pqrswxyznef。
————————————日志到此結束——————————————