今天继续学习文件与io,主要是学习文件共享及文件、复制文件描述符,有点抽象,主要是概念上的理解,但是很重要,下面一一来分解:

文件共享:
回顾一下,在linux系统调用中,是通过文件描述符来访问文件的,文件描述符是一个非负的整数,这是站在用户的观点来看的,实际上在linux内核上是有一定的数据结构来表示文件描述符的,下面就从三方面来看图分析一下内核中是怎么来表示打开的文件的:
一个进程打开两个文件内核数据结构:
 
(说明:关于这点,可以看一下我之前的博客:http://www.cnblogs.com/webor2006/p/3487718.html)
所以,由于文件描述符0、1、2被占用了,则我们用户打开的文件描述符只能从第3开始了:
如图上所示,对于内核,当打开一个文件时,会有一张表格来记录文件的状态,回顾一下,当我们在读取文件内容的时候,会自动的从当前文件的偏移位置去读取下一个数据,在文件的随机读写中已经介绍过(http://www.cnblogs.com/webor2006/p/3493218.html),原因就在于这个偏移量就保存在文件表当中的:
 
而每当我们打开一个文件时,内核就会为文件分配一个文件表,里面有不同的项,其中当前文件偏移量就是一个文件表项
回忆一下,我们打开一个文件,可以以读、写、追加、同步、非阻塞(这个之后会学到)等方式打开,用来描述它的就是另一个文件表项:文件状态标志
 
另外图中还有一个文件引用计数,它是用来描述一个文件被多少个文件描述符指向了(这个在下面的复制文件描述符中就可以体会到了):
 
另外,还有一个文件表项,它是v节点指针,它指向了v节点表,如图所示:
其中v节点表中,存放了两项很重要的信息,一个是v节点信息,一个是i节点信息:
v节点信息:我们上节学习的stat函数获得文件信息返回的状态信息就全保存在v节点信息里(http://www.cnblogs.com/webor2006/p/3496281.html):
i节点信息:当我们打开一个文件时,会将文件系统当中的i结点数据拷贝到v节点表中的i节点信息所存放的位置,比如说:
 
一个进程两次打开同一个文件内核数据结构:
 
下面以具体代码来进行说明:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int fd1;
int fd2;
char buf1[] = {};
char buf2[] = {};
fd1 = open("test.txt", O_RDONLY);//以只读的方式打开文件
if (fd1 == -)
ERR_EXIT("open error");
read(fd1, buf1, );
printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR);//以读写的方式打开文件
if (fd2 == -)
ERR_EXIT("open error"); read(fd2, buf2, );
printf("buf2=%s\n", buf2); close(fd1);
close(fd2);
return ;
}

先新建一个test.txt,里面输点测试内容:

编译运行:

也就是各个描述符有各自的偏移量,当buf1输出ABCDE时,如果第二个描述符共享偏移量的话,应该buf2输出FGhel,但是buf2输出的也是ABCDE,也就说明了各个文件描述符有不同的文件表项。

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
int fd1;
int fd2;
char buf1[] = {};
char buf2[] = {};
fd1 = open("test.txt", O_RDONLY);
if (fd1 == -)
ERR_EXIT("open error");
read(fd1, buf1, );
printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR);
if (fd2 == -)
ERR_EXIT("open error"); read(fd2, buf2, );
printf("buf2=%s\n", buf2);
write(fd2, "AAAAA", 5); memset(buf1, 0, sizeof(buf1));
read(fd1, buf1, 5);
printf("buf1=%s\n"
, buf1);
close(fd1);
close(fd2);
return ;
}

编译运行:

这结果为什么是它呢?下面来分析下:

write(fd2, "AAAAA", 5);

这时我们先来查看下test.txt的内容:

由于它会改变v节点表中的i节点信息所指向磁盘中的数据,而两个文件描述符的v节点表是共享的,而fd1此时的偏移量为5:

总结:每打开一个文件描述符,就有一个对应的文件表项描述,而如果打开的是同一个文件,v节点表是共享的 
 
两个进程打开同一个文件内核数据结构:
说明:不同的进程可以打开同一个文件,但是每个进程的文件描述符对应一个独立的文件表项,而最终共享v节点表。
总结:文件描述符跟文件不是一一对应的,文件描述符可以有多个,但是文件可以只有一个。
 
理解了打开的文件在内核中的结构,进而我们就可以理解复制文件描述符是怎么一回事了,如下:
复制文件描述符:
先用图来进行说明:
 
其中复制文件描述符,可以执行dup命令,注意,这时它会从0开始找出有空闲的文件描述符,如图,0、1、2是已经默认被系统给占用了,这时,执行dup之后,就会找到空闲的fd 4文件描述符,将它也指向同一个文件表,如图:
下面,以一段程序来说明一下输出重定向的原理,先复习一下什么是输出重定向:
下面,以具体程序来说明它,利用的就是复制文件描述符的知识:
先本地建一个空的test2.txt文件:
分析一下这个程序:
close(1)的作用,就是为了让输出到屏幕的文件描述符成为空闲的,然后dup时,会从0开始找空闲文件描述符,发现1是空闲的,则这时它的内存模型就变成这样了:
所以,清楚了它之后,对于ls > aa这样的输出重定向的功能,就比较容易实现了。
另外,对于复制文件描述符有三种方法:
对于dup2,理解它,我们可以将上面复制文件描述符的程序用dup2代替dup,如下:
编译运行:
 
另外第三种复制文件描述符的方法,是通过fcntl函数,它稍复杂一些,这个会在下节详细进行分析,先看一下man帮助:
其中第三个参数,可以决定复制文件描述符时,从第几个描述符开始搜索空闲,利用dup实现复制文件描述符时都是从0开始搜索的。
好了,关于fcntl的使用,下节再见!

linux系统编程之文件与io(四)的更多相关文章

  1. linux系统编程之文件与io(一)

    经过了漫长的学习,C语言相关的的基础知识算是告一段落了,这也是尝试用写博客的形式来学习c语言,回过头来看,虽说可能写的内容有些比较简单,但是个人感觉是有史起来学习最踏实的一次,因为里面的每个实验都是自 ...

  2. linux系统编程之文件与io(五)

    上一节中已经学习了文件描述符的复制,复制方法有三种,其中最后一种fcntl还并未使用到,关于这个函数,不光只有复制文件描述符的功能,还有其它一些用法,本节就对其进行一一剖析: fcntl常用操作: 这 ...

  3. linux系统编程之文件与io(二)

    今天继续学习文件与io,话不多说,开始进入正题: 文件的read和write系统调用: 说明:函数中出现在size_t和ssize_t是针对系统定制的数据类型:     下面以一个实现文件简单拷贝的示 ...

  4. linux系统编程之文件与IO(四):目录访问相关系统调用

    1. 目录操作相关的系统调用     1.1 mkdir和rmdir系统调用     1.1.1 实例     1.2 chdir, getcwd系统调用     1.2.1 实例     1.3 o ...

  5. linux系统编程之文件与IO(一):文件描述符、open,close

    什么是IO? 输入/输出是主存和外部设备之间拷贝数据的过程 设备->内存(输入操作) 内存->设备(输出操作) 高级I/O ANSI C提供的标准I/O库称为高级I/O,通常也称为带缓冲的 ...

  6. linux系统编程之文件与IO(七):时间函数小结

    从系统时钟获取时间方式 time函数介绍: 1.函数名称: localtime 2.函数名称: asctime 3.函数名称: ctime 4.函数名称: difftime 5.函数名称: gmtim ...

  7. linux系统编程之文件与IO(三):利用lseek()创建空洞文件

    一.lseek()系统调用 功能说明: 通过指定相对于开始位置.当前位置或末尾位置的字节数来重定位 curp,这取决于 lseek() 函数中指定的位置 函数原型: #include <sys/ ...

  8. linux系统编程之文件与io(三)

    上次我们利用文件的read和write来实现了简易的cp命令,其中将源文件拷贝到目标文件时,我们给目标文件的权限是写死的,而非根据源文件的权限生成的,如下: 今天就来解决这个问题,来学习获取文件权限相 ...

  9. linux系统编程之文件与IO(八):文件描述符相关操作-dup,dup2,fcntl

    本节目标: 1,文件共享 打开文件内核数据结构 一个进程两次打开同一个文件 两个进程打开同一个文件 2,复制文件描述符(dup.dup2.fcntl) 一,文件共享 1,一个进程打开两个文件内核数据结 ...

随机推荐

  1. DB2执行计划分析

    多表连接的三种方式详解 hash join.merge join. nested loop 项目中的SQL执行效率太低,就用执行计划看一下执行SQL,看不懂,百度一下,纪录下来: 大多数人从来没有听说 ...

  2. Xshell设置运行自动化脚本

    使用Xshell工具连接操作Linux系统,并编写运行自动化脚本示例: 这里介绍一种自动化下载日志文件的例子,下面先贴上编写的脚本,这里脚本命名为cyp-assout-log.js 如下: /* xs ...

  3. sql 表的连接 inner join、full join、left join、right join、natural join

    一.内连接-inner jion : SELECT * FROM table1 INNER JOIN table2 ON table1.field1 compopr table2.field2 INN ...

  4. Gevent模块,协程应用

    Gevent官网文档地址:http://www.gevent.org/contents.html 进程.线程.协程区分 我们通常所说的协程Coroutine其实是corporate routine的缩 ...

  5. 14 SpringMVC框架的基本概念

    1.三层架构 我们的开发架构一般都是基于两种形式,一种是 C/S 架构,也就是客户端/服务器,另一种是 B/S 架构,也就是浏览器服务器.在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发. ...

  6. Python基础系列讲解-自动控制windows桌面

    原链接:https://zhuanlan.zhihu.com/p/73001806 在使用PC时与PC交互的主要途径是看屏幕显示.听声音,点击鼠标和敲键盘等等.在自动化办公的趋势下,繁琐的工作可以让程 ...

  7. @Autowired 和 @Resource注解, 一个接口有多个实现类的时候Spring注入遇到的问题

    先说下我遇到的问题,有一个接口 CompensationService, 有两个实现类 MusicCompensationStrategyImpl  和  TakeDeliveryCompensati ...

  8. change事件传值方式 data-set 以及复杂数据类型 可以动态创建对象push到数组里面

    <template>   <div>     <!-- <div class="banner">       <a-breadcru ...

  9. Vasya and Shifts CodeForces - 832E (高斯消元)

    大意: 给定$4n$个$m$位的五进制数, $q$个询问, 每个询问给出一个$m$位的五进制数$b$, 求有多少种选数方案可以使五进制异或和为$b$. 高斯消元入门题 每次询问相当于就是给定了$m$个 ...

  10. SQL Server 索引优化-----数据库引擎优化顾问

    本文将根据“数据库引擎优化顾问”(DTA)来发现无用或缺失的索引. 要使用“数据库引擎优化顾问”,首先需要对数据库负载进行监控,为数据库负载分析准备数据.从SSMS的工具中,打开SQL Server  ...