首页 > 代码库 > Linux环境编程之文件I/O(四):文件I/O的数据结构

Linux环境编程之文件I/O(四):文件I/O的数据结构

(一)

Linux系统支持不同进程间共享打开的文件。内核使用三种数据结构表示打开的文件:进程表项、文件表项、v节点表。


1、进程表项:每个进程在进程表中都有一个记录项,记录项中年包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

a、文件描述符标志

b、指向一个文件表项的指针

2、内核为所有打开文件维持一张文件表。每个文件表项包含:

a、文件状态标志,如读写、添加、同步和非阻塞等。

b、当前文件偏移量

c、指向该文件v节点表项的指针

3、每个打开的文件都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点。


如果两个独立进程各自打开了同一个文件,假定第一个进程在文件描述符3上打开该文件,而另一个进程则在文件描述符4上打开该文件。打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的理由是:这样使得每个进程都有它自己的对该文件的当前偏移量。如下图:


注意三个概念:文件描述符标志、文件状态标志、文件描述符。

文件描述符标志(close_on_exec),它仅仅是一个标志,当fork了一个子进程,然后在子进程中调用了exec函数时就用到了该标志,它的含义就是,执行exec钱是否要关闭这个文件描述符,它的作用域只用于一个进程的一个描述符。而文件状态标志是在系统文件表中,读写可执行等这些标志,适用于指向该给定文件表项的任何进程中的所有描述符。


(二)

既然文件可以共享,那不同进程间共享同一个文件时,就可能出现并发竞态等问题。针对不同的竞态问题,我们有不同的策略。

1、若两个进程同时对一个文件进行写操作,则可能使其中一个的写数据被另外一个的写数据覆盖,针对这种情况,采取的办法是在open打开文件时设置O_APPEND标志。

2、Linux还允许原子性地定位搜索(seek)和执行I/O。其中pread和pwrite就是这种的函数。

       #include <unistd.h>
       ssize_t pread(int fd, void *buf, size_t count, off_t offset);
       ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

调用pread相当于顺序调用lseek和read,但pread又与这种顺序调用有下列区别:

a、调用pread时,无法中断其定位和读操作。

b、不更改文件指针

调用pwrite相当于顺序调用lseek和write,但也与他们有类似的区别。


(三)

可以使用dup和dup2函数复制一个现存的文件描述符

       #include <unistd.h>
       int dup(int oldfd);   // 返回的新文件描述符一定是当前可用文件描述符中的最小数值。
       int dup2(int oldfd, int newfd);  // 可以用newfd参数指定新描述符的数值,如果newfd已经打开,则现先将其关闭。如果newfd等于oldfd,则dup2返回newfd,而不关闭它。

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int
main(int argc, char *argv[])
{
	int fd;
	int nfd;
	char buf[] = "fjakdjl";

	fd = open("test.txt", O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
	if(fd < 0)
			printf("open error\n");
	printf("fd = %d.\n", fd);
	//dup2
	nfd = dup2(fd,4);
	if(nfd < 0){
		printf("error!\n");	
	}
	printf("nfd = %d.\n", nfd);
	return 0;
}
编译测试:

编译
gcc dup.c
执行
./a.out
显示结果
fd = 3.
nfd = 4.

(四)

为保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性。Linux系统提供了sync、fsync、fdatasync三个函数。

       #include <unistd.h>
       int fsync(int fd);  //只对文件描述符fd指定的单一文件起作用,并且等待写磁盘操作结束,然后返回
       int fdatasync(int fd);      // 类似于fsync,但它只影响文件的数据部分,出数据部分外,fsync还会同步更新文件的属性。
       void sync(void);             // 只是对所有修改过的快缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作。