在前面的文章我们介绍过不带缓冲区的IO,这节我们主要介绍不带缓冲区的IO相关内容。原子操作对于文件共享是十分重要的,因此我们将介绍一些原子操作相关概念。

1:文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。当我们打开或者创建一个文件的时候,都会返回一个非负的整数,我们打开和创建文件都用这个非负的整数。
同时,通常在我们执行一个进程的时候,都会默认打开三个文件描述符,他们分别是标准输入(0:STDIN_FILENO)、输出(1:STDOUT_FILENO)、错误(2:STDERR_FILENO)相关联的。他们的宏定义在unistd.h文件中。
文件描述符上限是OPEN_MAX。

2:open函数介绍

函数原型:int open(const char* pathname, int flag);                                         int open(const char* pathname, int flag, mode_t mode);pathname:是打开文件的路径名。flag:是打开文件的一些选项。mode:只有我们需要创建文件的时候,才会将指定mode的模式。O_RDONLY:只以读的方式打开文件。O_WRONLY:只一写的方式打开文件。O_EXCL:测试文件受否存在,如果不存在则创建文件。(注:如果同时指定O_CREAT并且文件已经存在则出错)O_APPEND:每次写入都追加到文件末尾。O_CREAT:如果文件不存在,则创建文件。使用这个选项的时候,需要第三个参数。        mode:权限位,也就是我们使用ls -l显示的各种读写和执行权限的位。        S_IRWXU:用户有读写执行权限。        S_IRUSR:用户有读权限。        S_IWUSR:用户有写权限。        S_IXUSR:用户有执行权限。        S_IRWXG:同组用户有读写执行权限。        S_IRGRP:同组用户有读权限。        S_IWGRP:同组用户有写权限。        S_IXGRP:同组用户有执行权限。        S_IRWXO:其他用户有读写执行权限。        S_IROTH:其他用户有读权限。        S_IWOTH:其他用户有写权限。        S_IXOTH:其他用户有执行权限。O_TRUNC:如果文件存在的话,并且我们只为读写打开文件的话,那么文件长度将会截断为0。也就是文件长度会变为0,相当于我们将文件内容全部删去再重新写入。O_NOBLOCK:指定文件为非阻塞IO,正常情况下在我们写入文件的时候,我们都等待写入完成之后再返回。如果指定此flag,那么将直接返回,而不管是否写入完成。O_SYNC:每一次写操作完成之前都会更新文件的属性。O_RSYNC:等待任何对文件同一部分未决写操作完成。(这个标志和下面一个标志不是很理解,如果理解的话,请在评论区讨论。最好是能有一个实际的例子)。O_DSYNC:每次写等待物理操作完成。仅当文件属性根棍以反映文件数据变化时,此标志才会影响文件属性。open函数返回的文件描述符一定是当前可用的最小文件描述符。文件名和路径名的截断:这个概念我简单介绍一下,就是我们输入打开文件的路径名的时候,由于我们输入的太长了,我们无法识别那么长的路径名,就会保留其中的一部分。这样就无法识别出具体的打开的文件了(我们保存这部分路径名可能与其他文件的路径名重名)。我们这里可以使用pathconf获取一些路径名长度,我发现我的机器长度限制是4096。creat:创建文件函数原型:create(const char *pahtname, mode_t mode);此函数和open函数是有雷同的部分,不做了解。close:关闭打开的文件描述符(就是我们打开一个文件一定会用到一些资源去管理这个打开的文件,那么这里关闭就是释放这些被栈用的资源)函数原型:int close(int filedes);lseek函数:更改当前文件的偏移量,也就是我们从文件哪里开始读写文件。 当我们打开一个文件或者创建一个新的文件,文件偏移量默认为0。也就是说从文件开始位置读写文件。同时我们也可以用lseek函数将当前文件偏移量定位到文件指定的置为。 函数原型:lseek(int filedes, off_t offset, int whence);                                            lseek_64(int filedes, off64_t offset, int whence); filedes:是文件描述符。 offset:是偏移的大小。(可以为正<增加文件偏移>,也可以为负<减少文件偏移>) 关于off_t是否够用的问题,我在这里简单讲解一下。 但是我们看函数原型的时候,有32位和64位两种文件偏移量。如果是32位那么文件偏移量最大时2的32次方减一,64位也是如此。 32位的文件偏移量最大时2TB(2^31-1字节),64位文件最大偏移量2^33TB,应该足够使用了。如果是不同的平台会用不同的大小,这里只列举了32位和64位。具体实现还是靠硬件, 这里就不逐一列举了。 whence:是指定位置开始进行便宜。             SEEK_SET:即从文件开头为只进行便宜。             SEEK_CUR:从当前文件位置进行便宜。             SEEK_END:从文件末尾位置开始便宜。同时lseek也可以测试文件是否设置偏移量,如果文件描述符引用的是一个管道、套接字,则lseek返回-1.read函数:从文件中读取指定字节数目的数据。函数原型:ssize_t read(int filedes, void *buf, size_t nbytes);filedes:文件描述符。buf:读取到字节存放的缓冲区。nbytes:读取多少个字节。返回值:实际读取到的字节数。实际读取到字节数可能小于我们要求读取的字节数。(eg,当前文件位置到文件末尾还有三十个可读的字节,但是我们要求读取200个字节,这就返回30个字节。但是在下次读取的时候,返回30个字节。还有从网络中读取数据,有些时候可能由于网络延迟读取小于我们读取到的字节数)。write函数:向文件中写入指定字节数的数据。函数原型:ssize_t write(int filedes, const void* buf, size_t nbytes);同上,buf是写入字节存储位置。nbytes是写入的字节数。返回值是实际写入的字节数。同时,如果我们指定了O_APPEND,每次我们写入之前都将文件偏移量设置为文件末尾的位置。文件共享:在介绍文件共享之前,我们先介绍一下内核是使用什么结构表示打开的文件的。内核使用三种数据结构表示打开的文件。(1)每个进程都有一个记录项,记录项抱哈一张打开的文件描述符表,每个文件描述符占用一项。我们在进程中使用一个整型记录这个文件项的位置。        每个文件描述符相关联的有两部分:文件标志,指向文件表的指针。(2)对于打开的文件内核维持一张文件表。    文件表中有文件状态标志、当前文件的偏移量和v节点指针。(3)每个打开的文件都有一个v节点。    v点包含了文件类型和对该文件各种操作的函数指针。同时还有i节点信息,当前文件的长度。关于这些内容了解即可。如下图所示,可以很好的理解这三者之间的关系。

同时,当我们两个进程同时打开一个文件的时候,其结构是下图这样的。

根据上述内容可以理解,如果是多个进程同时向一个文件中写入数据的时候,可能产生预期不到的结果。如果想要避免这种问题,就得理解原子操作相关的概念。原子操作:        概念:不受其他影响的独立完成一个操作的全过程。如下程序,我们向一个文件尾部添加内容。        lseek(fd, SEEK_END, 0);        write(fd, buf, size);        如果是一个程序,那么我们这样写没什么问题。但是如果是两个独立的进程,那么这样写可能就麻烦了(如两个进程分别是进程A和进程B,        如果进程A需要写100字节,进程B需要写入1000字节。那么可能在进程A写入50字节的时候进程B开始写入,并且进程B写入1000字节。那么这是进程A写入就会覆盖        进程B写入开头的50字节,这样记录的信息可能就出现了损坏)。如下图所示,两个文件向一个文件写入内容。

dup和dup2函数:复制文件描述符。函数原型: int dup(int filedes);                                            int dup2(int filedes, int filedes2);前一个函数返回一个新的文件描述符,这个新的文件描述符指向的文件表项和filedes文件描述符指向的文件表项是同一个(返回的文件描述符一定是当前进程表项中最小的文件描述符)。第二个函数使用指定的文件描述符去复制filedes文件描述符,如果filedes2已经使用,则先关闭filedes2,然后在复制filedes。参考下述图片,去理解复制一个文件描述符的意义。

前面曾经提到过IO缓冲区,当我们使用缓冲区进行向磁盘写入数据的时候,有些事后我们可能无法及时将数据写入到磁盘中。当我们的机器意外出现事故的时候,我们可能丢失数据。还有就是一些数据库需要及时的保存,因此、我们需要让写这个操作及时进行,下面有三个函数会将数据及时写入到磁盘中。函数原型:int fsync(int filedes);将指定文件描述符数据写入到磁盘中,这里是等待写操作结束返回。                                         int sync();将所有修改过的缓冲区排入写操作队列。                                         int fdatasync(int filedes);将数据写入到磁盘的同时还将文件属性更新。前面曾经文件的一些性质,这里呢有个函数可以通过文件描述符改变打开文件的一些性质。函数原型:int fcntl(int filedes, int cmd, ...);                (1):复制现有的描述符。                (2):获得/设置文件描述符标志。                (3):获得/设置文件状态标志。                (4):获得/设置异步I/O所有权(异步IO是通过信号的方式实现的,在讲解套接字部分会有所讲解)。                (5):获得/设置记录所参看man手册,我们会发现如下许多cmd。        F_DUPFD:复制一个新的文件描述符。        F_GETFD/F_SETFD:获得和设置文件描述符标记。当前的标记只有一种,就是FD_CLOEXEC(在执行exec函数的时候关闭该文件描述符)。        F_GETFL/F_SETFL:获得和设置文件状态标记。文件状态标记在前面除了O_DSYNC和O_SYNC都可以改变。(eg:读,写,追加等等)我这里参考的是我这个版本的man手册,        如果想要了解请参考你的版本man手册。        F_GETOWN/F_SETOWN:获得和设置异步IO所有权。在学习套接字部分将会了解到这里。        关于记录锁,使用如下图的结构。        F_SETLK/F_SETLKW/F_GETLK:请求或者释放一个锁/设置一个文件锁/返回一个锁(如果没有锁,那么在l_type字段设置为F_UNLCK)。        在学习多进程的时候,我会写一个实验关于文件锁的,就是多个进程读写文件,使用文件的记录锁实现文件共享。![file](http://jeff.spring4all.com/FhnY51hz_j5wXkeBMnrJlv0Qn7_Y)
©著作权归作者所有:来自51CTO博客作者lvan_linux的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 从.env文件中为NodeJS加载环境变量[每日前端夜话0xA9]
  2. Centos给文件设置了777权限仍不能访问解决方案
  3. 用原生 JavaScript 实现十大 jQuery 函数[每日前端夜话0x94]
  4. 如何设计一个牛逼的文件搬运工?
  5. shell脚本之灵活调用函数技巧
  6. 共享文件夹
  7. 从一份定义文件详解ELK中Logstash插件结构
  8. Redhat 7 LVM xfs文件系统修复
  9. 函数式编程思维在三行代码情书中的应用

随机推荐

  1. Android热修复实现
  2. Android自定义Toast带图片的
  3. android中一些能在国外使用的第三方地图
  4. 关于selenium android下的浏览器测试
  5. Android中的AlertDialog使用示例四(多项选
  6. 屏幕中央的TextView [重复]
  7. [置顶] Android 开发笔记
  8. android 利用广播实现程序的强制退出
  9. android 平台的RNDIS功能
  10. 关于activity之间及activity与baseAdapte