Linux系統的文件定位、共享操作

lseek函數

  • lseek(lseek函數用於改變文件的當前偏移量。)
  • 頭文件
    #include<unistd.h>
  • 定義函數
    off_t lseek(int filedes, off_t offset, int origin);
  • 函數說明
    filedes 文件描述符
    offset 必須與origin一同解析
    origin爲 SEEK_SET, 則offset從文件的開頭算起。
    origin爲 SEEK_CUR, 則offset從當前位置算起,既新偏移量爲當前偏移量加上offset
    origin爲 SEEK_END, 則offset從文件末尾算起。
  • 返回值
    如果失敗,返回值爲-1,成功返回移動後的文件偏移量。可用lseek確定文件當前偏移量:currpos = lseek( fd, 0, SEEK_CUR)
  • lseek常用於找到文件的開頭、找到文件的末端,判定文件描述符的當前位置
  • lseek僅將當前的文件偏移量記錄在內核中,它並不引起任何I/O操作,然後該偏移量用於下一次讀、寫操作。
  • 文件偏移量可以大於文件的當前長度,但並不改變相應的i節點信息。在這種情況下的下一次寫將延長該文件,並在文件中構成一個空洞,但文件大小並不是文件的最大偏移量。對空洞位置的讀操作將返回0。
//lseek實現空洞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	int fd;
	int ret;
	fd = open("hole.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
	if(fd == -1)
		exit(1);
		//perror("lseek error");
	write(fd,"hello",5);
	ret = lseek(fd,1024*1024*1024,SEEK_CUR);
	if(ret == -1)
		exit(2);
		//perror("lseek error");
	write(fd,"world",5);
	close(fd);
	return 0;
}
  • 執行上述代碼結果:
    在這裏插入圖片描述

pread函數

  • pread(在給定的偏移量讀取一個文件描述符)
  • 頭文件
    #include<unistd.h>
  • 函數原型
    ssize_t pread (int fd, void *buf, size_t count, off_t pos)
  • 返回值
    返回讀到的字節數;出錯:返回-1;到文件結尾:返回0
  • 解決問題
    由於lseek和read 調用之間,內核可能會臨時掛起進程,所以對同步問題造成了問題,調用pread相當於順序調用了lseek 和read,這兩個操作相當於一個捆綁的原子操作
  • 其他
    調用pread時,無法中斷其定位和讀操作,另外不更新文件指針

pwrite函數

  • pwrite(在給定的偏移量寫入一個文件描述符)
  • 頭文件
    #include<unistd.h>
  • 函數原型
    ssize_t pwrite (int fd, const void *buf, size_t count, off_t pos )
  • 返回值
    返回已寫的字節數;出錯:返回-1
  • 解決問題
    由於lseek和write 調用之間,內核可能會臨時掛起進程,所以對同步問題造成了問題,調用pwrite相當於順序調用了lseek 和 write,這兩個操作相當於一個捆綁的原子操作
  • 其他
    調用pwrite時,無法中斷其定位和讀操作,另外不更新文件指針

與read/write的區別

  1. 調用更容易使用,特別是在進行需要技巧的操作時
  2. 完成工作後不會改變文件指針
  3. 避免使用lseek時可能造成的競爭條件
  4. 如果有多個線程共享文件描述符,當第一個線程調用lseek之後,在它進行讀取或寫入操作之前,同一個程序中的另一個線程可能會改變文件的位置

文件共享

  • UNIX/Linux支持不同進程間共享文件。內核使用的三種表(文件描述符表、文件表、索引結點表)之間的關係決定了在文件共享方面一個進程對另一個進程可能產生的影響
  • 每個進程在進程表中有一個文件描述符表,每個描述符表項指向一個文件表
  • 內核爲每一個被打開的文件維護一張文件表,文件表項包含
    • 文件的狀態標誌(讀、寫、同步、非阻塞)
    • 文件當前位置
    • 指向該文件索引節點表的指針
  • 每個文件(或設備)都有一個索引節點,它包含了文件類型屬性及文件數據
  • 如果兩個進程分別打開同一個的文件(物理文件),則它們有不同的文件表,因此每個進程有自己的文件當前位置,因此其讀寫操作互不影響。
  • 也存在不同進程共享同一個文件表(父子進程),或同一進程共享同一個文件表(dup操作)。此時,兩個進程對該文件的讀寫操作將基於同一個文件當前位置。

不同進程共享相同文件

在這裏插入圖片描述
在這裏插入圖片描述

同一進程共享相同文件

在這裏插入圖片描述
在這裏插入圖片描述

dup和dup2

  • 頭文件:unistd.h
  • 函數原型
    int dup( int oldfd );
    int dup2( int oldfd, int targetfd );
  • 函數說明:複製一個文件的描述符,dup2函數跟dup函數相似,但dup2函數允許調用者規定一個有效描述符和目標描述符的
  • 返回值:dup2函數成功返回時,目標描述符
  • 示例
int fd1, fd2;
...
fd2 = dup( fd1 );
int oldfd;
oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 );
dup2( oldfd, 1 );	//1代表stdout
close( oldfd );

線程共享文件

  • 線程的定義:有時稱輕量級進程,是進程中的一個執行線路或線索,是一個相對獨立的、可獨立調度和指派的執行單元
  • 線程的創建:應用程序可以通過一個統一的clone()系統調用接口,用不同的參數指定創建輕量進程還是普通進程。
  • clone()調用do_fork()創建線程, do_fork()參數爲:
    (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    //其中do_fork()爲fork()和vfork()的底層操作。
  • CLONE_VM:do_fork()需要調用copy_mm()來設置task_struct中的mm和active_mm項,這兩個mm_struct數據與進程所關聯的內存空間相對應。如果do_fork()時指定了CLONE_VM開關,copy_mm()將把新的task_struct中的mm和active_mm設置成與current的相同,同時提高該mm_struct的使用者數目
    (mm_struct::mm_users)。也就是說,輕量級進程與父進程共享內存地址空間。
  • CLONE_FS:task_struct中利用fs(struct fs_struct *)記錄了進程所在文件系統的根目錄和當前目錄信息,do_fork()時調用copy_fs()複製了這個結構;而對於輕量級進程則僅增加fs->count計數,與父進程共享相同的fs_struct。也就是說,輕量級進程沒有獨立的文件系統相關的信息,進程中任何一個線程改變當前目錄、根目錄等信息都將直接影響到其他線程。
  • CLONE_FILES:一個進程可能打開了一些文件,在進程結構task_struct中利用files(struct files_struct *)來保存進程打開的文件結構(struct file)信息,do_fork()中調用了copy_files()來處理這個進程屬性;輕量級進程與父進程是共享該結構的,copy_files()時僅增加files->count計數。這一共享使得任何線程都能訪問進程所維護的打開文件,對它們的操作會直接反映到進程中的其他線程。
  • CLONE_SIGHAND:每一個Linux進程都可以自行定義對信號的處理方式,在task_struct中的sig(struct signal_struct)中使用一個struct k_sigaction結構的數組來保存這個配置信息,do_fork()中的copy_sighand()負責複製該信息;輕量級進程不進行復制,而僅僅增加signal_struct::count計數,與父進程共享該結構。也就是說,子進程與父進程的信號處理方式完全相同,而且可以相互更改。
  • 總結:線程間所有文件結構都爲共享資源,不但“文件表項”(file對象)是共享的,就連“文件描述符表”(files_struct結構)也是共享的。線程的創建僅僅增加的是files和fs的引用計數,“文件打開計數”(file對象的引用計數)並沒有增加,所以任何一個線程對打開的文件執行close操作,文件都將關閉(文件打開計數爲1的情況)。但是如果線程不進行打開文件的關閉,則文件直到進程結束時纔會關閉,這就是使用多線程實現tcp服務器時,服務線程必須要顯示調用close的原因,否則永遠不會發送FIN終止鏈接(因爲主線程一直處於監聽不會結束)。

進程間文件描述符的傳遞

◼ 傳遞描述符的函數的參數是fd,fd是打開文件指針在數組中的下標
◼ 將一個文件描述符傳遞給另一個進程後,文件的“訪問計數”會增加
◼ 進程間傳遞文件描述符可以看做跨進程的dup調用,也就是同一個file對象在不同進程間的映射
◼ 對於網絡接口返回的描述符 ,只能採取傳遞文件描述符的方法。
◼ UNIX系統中兩個方法:BSD sendmsg,recvmsg方法;SYSV ioctl方法
◼ 進程間傳遞文件描述符時,發送進程和接收進程共享同一文件表項
◼ 進程間文件描述符的傳遞,只是通過內核將接收文件的一個新file指針指向和發送進程的同一個file對象,並使這個file對象的引用計數增加
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章