linux常用文件I/O操作之文件共享的实现方式




 1、文件共享的三种实现方式

    1、什么是文件共享:   

    (1)文件共享就是同一个文件(同一个文件指的是同一个inode,同一个pathname)被多个独立的读写体(几乎可以理解为多个文件描述符)去同时(一个打开尚未关闭的同时另一个去操作)操作。

    (2)文件共享的意义有很多:譬如我们可以通过文件共享来实现多线程同时操作同一个大文件,以减少文件读写时间,提升效率。

    2、文件共享的核心就是怎么弄出来多个文件描述符指向同一个文件。

    3、常见的三种文件共享情况:

        1、是同一个进程中多次使用open函数打开同一个文件。

        2、不同进程中分别使用open函数打开同一个文件(因为此时两个fd不在同意进程中,所以两个fd可能相同,也可能不同)。

        3、linux提供的dup和dup2两个API来让进程赋值文件描述符。

    4、我们分析文件共享的核心关注点在于:分别写、读还是连续读写

补充:

再论文件描述符:

    1、文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。

    2、文件描述符这个数字是open系统调用内部由操作系统自动分配的,操作系统分配这个fd也不是随机分配的,也会按照一定的规律。

    3、操作系统规定,fd从0开始一次增加,fd也是有最大限制的,在linux的早期版本中fd最大只有20.linux中文件描述符表是一个数组(而不是链表),所以这个文件描述符表其实就是一个数组,fd是index,文件表指针是value

    4、当我们open时,内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回。

文件描述符的复制:

    5、fd中0、1、2已经默认被系统占用了,当我们运行一个程序得到一个进程时,内部就默认已经打开了3个文件,这三个文件对应的fd就是0、1、2。这三个文件分别叫stdin、stdout、stderr。也就是标准输入、标准输出、标准错误。因此用户进程得到的最小的fd就是3了。

    6、标准输入一般对应的是键盘(可以理解为:0这个fd对应的是键盘的设备文件),标准输出一般是LCD显示器(可以理解为:1对应LCD的设备文件)

    7、printf函数其实就是默认输出到标准输出stdout上了。stdio中还有一个函数叫fpirntf,这个函数就可以指定输出到哪个文件描述符中。

2、如何实现文件共享

    1、同一个进程中多次使用open函数打开同一个文件。同时打开同一个文件不难理解,关键在于打开同一个文件后,我们读取写入文件时,是分别写呢还是接续写,简单理解就是,覆盖着写呢?还是一个写完了,文件指针自动挪到后边分开写?我们不用去猜,记别人的理论,我们直接自己用代码测试,只要代码正确的情况测试的结果就是理论。下来我们来看测试代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>


typedef int file_t;  
#define MAXLENG 1024

int main(int argc,char *argv[])
{
   file_t  fd1 = -1, fd2 = -1;
  	
   fd1 = open("./1.txt",O_RDWR | O_TRUNC | O_CREAT,0644);

   if(fd1 < 0)
   {
	   perror("fd1 :open file failed :");
	   _exit(-1);
   }

   fprintf(stdout,"fd1 = %d\n",fd1);
   
   
//这里为了区别更明显,	fd2的open以及判断完全可以放在fd1的判断中去
   fd2 = open("./1.txt",O_RDWR | O_TRUNC | O_CREAT,0644);

   if(fd2 < 0)
   {
	   perror("fd2:open file fialed :");
	   _exit(-1);
    }

   fprintf(stdout,"fd2 = %d\n",fd2);

   while(1)
   {
	  
	   write(fd1,"aaaa",4);
	   sleep(1);
	   write(fd2,"bbbb",4);
   }
   
   return 0;

}

这段代码运行完成以后,我们来看结果

[root@xiao_k filetest]# cat 1.txt 
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaa[root@xiao_k filetest]#

    这样我们可以很容易看到,我们虽然是用两个文件指针分别写,但是fd1只有在最后一次写进去,对吧?这时我们看到的;但是这为什么会这样呢?这里我们分析一下:其实fd1进行了写入操作,只是我们fd1和fd2两个文件指针是不同的文件指针,当fd1写入以后,文件指针往后移动,但是fd2并没有移动,当fd2进行写入时就会覆盖掉fd1写入的内容,并且这里与你两次打开文件的属性有关,这里使用

O_RDWR | O_TRUNC | O_CREAT三个属性打开,我们也可以自己测试其他属性打开的结果。虽然不同属性打开的结果可能造成的结果不同,但是我们这里是可以确定的,一个文件指针移动后并不会造成另外一个文件指针移动

    2、第二种方法用不同进程中分别使用open函数打开同一个文件(因为此时两个fd不在同意进程中,所以两个fd可能相同,也可能不同)。这里还没有总结fork函数产生新的进程。所以先不总结,博主后边补充上去。


    3、使用linux提供的dup和dup2两个API来让进程赋值文件描述符。

     1、使用dup进行文件描述符复制

      (1)dup系统调用对fd进行复制,会返回一个新的文件描述符(譬如原来的fd是3,返回的就是4)

      (2)dup系统调用有一个特点,就是自己不能指定复制后得到的fd的数字是多少,而是由操作系统内部自动分配的,分配的原则遵守fd分配的原则。

      (3)dup返回的fd和原来的oldfd都指向oldfd打开的那个动态文件,操作这两个fd实际操作的都是oldfd打开的那个文件。实际上构成了文件共享。

      (4)dup返回的fd和原来的oldfd同时向一个文件写入时,结果是分别写还是接续写?

使用dup的缺陷分析

      (1)dup并不能指定分配的新的文件描述符的数字,dup2系统调用修复了这个缺陷,所以平时项目中实际使用时根据具体情况来决定用dup还是dup2.

     2、使用dup2进行文件描述符复制

      (1)dup2和dup的作用是一样的,都是复制一个新的文件描述符。但是dup2允许用户指定新的文件描述符的数字。

      (2)使用方法看man手册函数原型即可。

测试使用dup和dup2共享文件操作:

    1、dup共享文件的实现

    

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>

typedef int file_t;
#define MAXSIZE 1024

int main(int argc,char *argv[])
{
	file_t fd1 = -1, fd2 = -1;
	fd1 = open("./3.txt",O_RDWR | O_CREAT | O_TRUNC,0644);

	if(fd1 < 0)
	 {
		perror("open fiel error :");
		_exit(-1);
	}
	
	fprintf(stdout,"fd1 = %d\n",fd1);

	fd2 = dup(fd1);

	if(-1 == fd2)
	{
		perror("dup error:");
		_exit(-1);
 	}

	fprintf(stdout,"fd2 = %d\n",fd2);

	while(1)
	{
		write(fd1,"aaaa",4);
		sleep(1);
		write(fd2,"bbbb",4);
	
	}



	return 0;
}

我们继续来看结果:

[root@xiao_k filetest]# cat 3.txt 
aaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaa
aabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaa
[root@xiao_k filetest]#

     从结果我们这次可以发现a和b是交替出现的,这也符合预期,dup虽然是产生了两个文件描述符,但是共用同一个文件指针,当一个写入以后,文件指针已经移动到后边去,自然不会覆盖掉了。


   2、dup2共享文件交叉写入测试

    1、对于dup2和dup写入文件一样,区别在于dup2可以自己指定文件描述符,所以这里就不给出dup2写入文件的实例。

    2、交叉写入的时候,结果是接续写

3、fcntl函数

    1、fcntl函数是一个多功能文件管理的工具箱,可以接受2个参数+1个变参,第一个参数是fd表示哟啊操作那个文件,第二个参数是cmd表示要进行那个命令操作。变参是用来传递参数的,要配合cmd命令使用

    2、cmd的样子类似于F_XXX,不同的cmd具有不同的功能。学习时,没有必要把所有的cmd含义弄清楚,只需要把一个弄明白就行,其他的查man手册即可。

    3、常用fcntl的cmd    

      1、F_DUPFD这个cmd的作用是复制文件描述符(作用类似于dup和dup2),这个命令的功能是从可用的fd数字列表中找一个比arg大或者和arg一样大的数字作为oldfd的一个复制的fd,和dup2有点像但是不同。dup2返回的就是我们指定的那个newfd否则就会出错,但是F_DUPFD命令返回的是>=arg的最小的那一个数字。

4、标准I/O库

  1、标准IO和文件IO有什么区别

    1、最大的区别:标准I/O库是C库函数,而文件I/O是linux系统调用的API

    2、C语言库函数是由API封装起来的,库函数内部也是通过调用API来完成操作的,但是库函数多了一层封装,所以比API更加好用。

    3、库函数比API还有一个优势,API在不同的操作系统的接口是不一样的是不能通用的,但是C库函数在不同操作系统上几乎是一样的,所以C库函数具有可移植性而API不具有可移植性。

   2、常用标准IO函数介绍:

        常见的标准IO库函数:fopen fclose fwrite fread ffulsh、fseek


    以上文档,本人学习参考了不同老师的文档自己总结而来,代码都是本人亲自测试。关于文档那块有问题,请各位指正。