UNIX环境高级编程(6):文件I/O(2)
文件共享:
UNIX系统支持在不同进程间共享打开的文件。
内核使用三种数据结构表示打开的文件。他们之间的关系决定了在文件共享方面一个进程对还有一个进程可能产生的影响:
(1)每一个进程在进程表中都有一个记录项。记录项中包括有一张打开文件描写叙述符表。可将其视为一个矢量。每一个描写叙述符都占用一项,与每一个文件描写叙述符相关联的是:
- 文件描写叙述符标志
- 指向一个文件表项的指针
(2)内核为全部打开文件维护一张文件表,每一个文件表项包含:
- 文件状态标志(读,写,添写,同步和非堵塞等);
- 当前文件偏移量;
- 指向该文件v节点表项的指针;
(3)每一个打开文件(或设备)都有一个v节点结构。v节点包括了文件类型和对此文件进行各种操作的函数的指针;对于大多数文件,v节点还包括了该文件的i节点(i-node,索引节点)。这些信息是打开文件时从磁盘上读入内存的。所以全部关于文件的信息都是高速可供使用的。
比如。i节点内包括了文件的全部者。文件长度。文件所在设备,指向文件实际数据在磁盘上所在位置的指针等等。
上面的讨论是概念性的,与特定实现可能匹配。也可能不匹配(Linux没有使用v节点,而是使用了通用i节点结构,可是在概念上,v节点与i节点是一样的。两者都指向文件系统特有的i节点结构,创建v节点结构的目的是对在一个计算机系统上的多文件系统类型提供支持。sun称这样的文件系统为虚拟文件系统,称与文件系统类型无关的i节点部分为v节点)。
假设两个独立进程各自打开了同一个文件,打开该文件的每一个进程都得到一个文件表项,但对一个给定的文件仅仅有一个v节点表项。每一个文件都有自己的文件表项的一个理由是:这样的安排使每一个进程都有它自己的对该文件的当前偏移量。
可能有多个文件描写叙述符项指向同一个文件表项。
比如dup函数,或者在fork后也会发生相同的情况,此时父、子进程对于每个打开文件文件描写叙述符共享同一个文件表项。
因此多个进程读同一文件都能正确工作。由于每一个进程都有它自己的文件表项,当中也有它自己的当前文件偏移量。可是多个进程写同一个文件时,则可能产生预期不到的结果。
原子操作:
添写至一个文件:
要在一个文件里进行添写,第一种方法是“定位到文件尾端,然后写”,它使用两个分开的函数调用(lseek。write)。这样的方法会出问题。不论什么一个须要多个函数调用的操作都不可能是原子操作,由于在两个函数之间,内核有可能会暂时挂起该进程。解决这个问题的方法是使这两个操作对于其它进程而言成为一个原子操作。
UNIX系统提供了一种方法使这样的操作成为原子操作,该方法是在打开文件时设置O_APPEND标志。这就使得内核每次对该文件进行写之前,都将进程的当前文件偏移量设置到该文件的尾端处,于是在每次写之前就不再须要调用lseek。
pread和pwrite:
SUS包括了XSI扩展。该扩展同意原子性地定位搜索(seek)与运行I/O。pread和pwrite就是这样的扩展:
#include <unistd.h>
ssize_t pread(int fieldes, void *buf, size_t nbytes, off_t offset);
返回值,读到的字节数,若已到文件结尾则返回0。若出错则返回-1;
ssize_t pwrite(int fieldes, const void *buf, size_t nbytes, off_t offset);
返回值,若成功则返回已写的字节数,若出错。则返回-1;
创建一个文件:
之前已经讲过open函数的O_CREAT和O_EXCL选项。当同一时候指定这两个选项。而该文件又已经存在时。open将失败。
这两个选项使得检查文件是否存在以及创建该文件这两个操作是作为一个原子操作运行的。
综上所述,原子操作指的是由多步组成的操作,假设该操作原子地运行,则要么运行全然部步骤。要么一步也不运行。不可能仅仅运行全部步骤的一个子集。
dup和dup2函数:
以下两个函数都可用来复制一个现存的文件描写叙述符:
#include <unistd.h>
int dup(int fieldes);
int dup2(int fieldes, int fieldes2);
两函数的返回值:若成功则返回新的文件描写叙述符,若出错则返回-1。
由dup返回的新文件描写叙述符一定是当前可用文件描写叙述符中的最小数值,用dup2则能够用fieldes2參数指定新描写叙述符的数值。假设fieldes2已经打开,则先将其关闭。
假设fieldes等于fieldes2,则dup2返回filedes2。而不关闭它。
这些函数返回的新文件描写叙述符与參数filedes共享同一个文件表项。由于两个描写叙述符指向同一文件表项,所以它们共享同一文件状态标志(读、写、添写等)以及同一当前文件偏移量。
可是每一个文件描写叙述符都有它自己的一套文件描写叙述符标志。
拷贝文件描写叙述符的还有一种方法是使用fcntl函数:
- 调用dup(filedes) 等效于fcntl(filedes, F_DUPFD, 0);
- 而调用dup2(filedes, filedes2)等效于close(filedes2); fcntl(filedes,
F_DUPFD, filedes2);
可是后一种情况,dup2并不全然等同于close加上fcntl。
它们之间的差别在于:
- dup2是一个原子操作。而close及fcntl则包括两个函数调用;
- dup2和fcntl有某些不同的errno。
sync、fsync、fdatasync函数:
传统的UNIX实如今内核中设有缓冲区快速缓存或页面快速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据拷贝到当中一个缓冲区中,假设该缓冲区尚未写满。则并不将其排入输出队列,而是等待其写满或者内核须要重用该缓冲区以存放其他磁盘块数据时。再将该缓冲排入到输出队列,然后待其到达队首时,才进行实际的I/O操作。
这样的输出方式被称为“延迟写”。
延迟写降低了磁盘读写次数。可是却降低了文件内容的更新速度,使得欲写到文件里的数据在一段时间内并没有写到磁盘上。当系统发生问题时,这样的延迟可能造成文件更新内容的丢失。
为了保证磁盘上实际文件系统与缓冲区快速缓存中内容的一致性,UNIX系统提供了sync,fsync和fdatasync三个函数。
#include <unistd.h>
int fsync(int filedes);
int fdatasync(int filedes);
返回值:若成功则返回0。若出错则返回-1;
void sync(void)。
sync函数仅仅是将全部改动过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
通常称为update的系统守护进程会周期性地调用sync函数,命令sync(1)也调用sync函数。fsync函数仅仅对由文件描写叙述符filedes指定的单一文件起作用,而且等待写磁盘操作结束,然后返回。fdatasync函数类似于fsync。但它仅仅影响文件的数据部分。而fsync还会同步更新文件的属性。
fcntl函数:
fcntl函数能够改变已打开文件的性质:
#include <fcntl.h>
int fcntl(int filedes, int cmd, ... /* arg */);
返回值:若成功则依赖于cmd。若出错则返回-1。
fcntl函数有5种功能:
- 复制一个现有的描写叙述符(cmd = F_DUPFD);
- 获得/设置文件描写叙述符标记(cmd = F_GETFD或F_SETFD);
- 获得/设置文件状态标志(cmd = F_GETFL 或 F_SETFL);
- 获得/设置异步I/O全部权(cmd = F_GETOWN 或 F_SETOWN)
- 获得/设置记录锁(cmd = F_GETLK F_SETLK 或 F_SETLKW)。
F_DUPFD:在上面已经讲过了。拷贝文件描写叙述符filedes,新文件描写叙述符作为函数值返回,它是尚未打开的各描写叙述符中大于或等于第三个參数值中各值的最小值。
F_GETFD:相应于filedes的文件描写叙述符标志作为函数值返回。当前仅仅定义了一个文件描写叙述符标志FD_CLOEXEC;
F_SETFD:对于filedes设置文件描写叙述符标志,新标志值按第三个參数设置。
F_GETFL:相应于filedes的文件状态标志作为函数值返回。在说明open函数时。已经说明了文件状态标志(注意,O_RDONLY、O_WRONLY、O_RDWR三个訪问方式标志并不各占一位。因此首先要用屏蔽字O_ACCMODE取得訪问模式位。然后将结果与这三种值中的不论什么一种作比較)。
F_SETFL:将文件状态标志设置为第三个參数的值,能够更改的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC。O_DSYNC。O_RSYNC。O_FSYNC、O_ASYNC。
F_GETOWN:取当前接收SIGIO和SIGURG信号的进程ID或进程组ID;
F_SETOWN:设置接收SIGIO和SIGURG信号的进程ID和进程组ID,正的arg指定一个进程ID。负的arg表示等于arg绝对值的一个进程组ID;
下列程序接受一个文件描写叙述符作为參数,打印该文件描写叙述符所指向的文件表项中的文件状态标志:
/*
* Copyright (C) fuchencong@163.com
*/ #include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> int
main(int argc, char *argv[])
{
int val; if (argc != 2) {
printf("usage: ./fcntl fd\n");
exit(1);
} if ( (val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) {
printf("fcntl error\n");
exit(1);
} switch (val & O_ACCMODE) {
case O_RDONLY:
printf("read only");
break; case O_WRONLY:
printf("write only");
break; case O_RDWR:
printf("read write");
break; default:
printf("unknown access mode");
break;
} if (val & O_APPEND) {
printf(", append");
}
if (val & O_NONBLOCK) {
printf(", nonblocking");
}
#if defined(O_SYNC)
if (val & O_SYNC) {
printf(", synchronous writes");
}
#endif
#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC)
if (val & O_FSYNC) {
printf(", synchronous writes");
}
#endif
putchar('\n'); exit(0);
}
上述使用了功能測试宏_POSIX_C_SOURCE,而且条件编译了POSIX.1中未定义的文件訪问标志。以下是该程序在bash中运行结果,当中5<>temp.foo表示在文件描写叙述符5上打开文件temp.foo以供读写:
在改动文件描写叙述符标志或文件状态标志时必须慎重,先要取得现有的标志值,然后依据须要改动它。最后设置新标志值。不能仅仅是运行F_SETFD或F_SETFL。这样会关闭曾经设置的标志位:
下列程序显示了对一个文件描写叙述符设置一个或多个文件状态标志的范例:
/*
* Copyright (C) fuchencong@163.com
*/ #include <fcntl.h>
#include <stdlib.h>
#include <stdio.h> void
set_fl(int fd, int flags)
{
int val; if ( (val = fcntl(fd, F_GETFL, 0)) < 0) {
printf("get file flag error\n");
return ;
} val |= flags; if (fcntl(fd, F_SETFL, val) < 0) {
printf("set file flag error\n");
}
}
假设将 var |= flags 改为 val &= ~flags则用来关闭某个标志位。
程序执行时,设置O_SYNC标志会添加时钟时间。由于每次write操作都要等待,直至数据已写到磁盘上再返回。
所以当支持同步写时,系统时间和时钟时间应当会显著添加。
尽管能够在调用open函数时就设置文件状态标志。可是fcntl函数仍然很有必要。fcntl函数同意在仅知道文件描写叙述符的情况下改动其性质。
比如,标准输出是由shell打开的,因此我们无法通过open函数来设置其文件状态标志,可是通过fcntl函数能够做到。
ioctl函数:
ioctl函数是I/O操作的杂物箱,不能用其他函数表示的I/O操作通常都能用ioctl表示,终端I/O是iotcl的最大使用方面。
#include <unistd.h> /* System V */
#include <sys/ioctl.h> /* BSD and Linux */
#include <stropts> /* XSI STREAMS */
int ioctl(int filedes,int request,...);
iotcl函数仅仅是SUS标准的一个扩展,以便处理STREAMS设备,可是UNIX系统实现用它进行非常多杂项设备操作。有些实现甚至将它扩展到用于普通文件。
在此函数原型中,我们表示的仅仅是iotcl函数本身所要求的头文件。通常,还要求另外的设备专用头文件。每一个设备驱动程序都能够定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令。
/dev/fd
较新的系统都提供名为/dev/fd的文件夹,其文件夹项是名为0,1,2等的文件,打开文件/dev/fd/n等效于复制描写叙述符n(假定描写叙述符n是打开的)。
在下列函数调用中:
fd = open("/dev/fd/0", mode);
大多数系统会忽略它所指定的mode,而另外一些则要求mode必须是所涉及的文件原先打开时所使用mode的子集。
上面的函数调用等效于fd = dup(0)。所以描写叙述符0和fd共享同一文件表项。
某些系统提供路径名/dev/stdin、/dev/stdout/和/dev/stderr,这些等效于/dev/fd/0、/dev/fd/1和/dev/fd/2。
/dev/fd文件主要由shell使用,它同意那些使用路径名作为调用參数的程序,能用处理其他路径名的同样方式处理标准输入和输出。尽管非常多程序都支持在命令行中使用"-"作为一个參数。特指标准输入或输出。可是/dev/fd则提高了文件名称參数的一致性,也更加清晰。
UNIX环境高级编程(6):文件I/O(2)的更多相关文章
- 【UNIX环境高级编程】文件I/O
[UNIX环境高级编程]文件I/O大多数文件I/O只需要5个函数: open.read.write.lseek以及close 不带缓冲的I/O: 每个read和write都调用内核中的一个系统调用 1 ...
- Unix环境高级编程:文件 IO 原子性 与 状态 共享
参考 UnixUnix环境高级编程 第三章 文件IO 偏移共享 单进程单文件描述符 在只有一个进程时,打开一个文件,对该文件描述符进行写入操作后,后续的写入操作会在原来偏移的基础上进行,这样就可以实现 ...
- Unix环境高级编程(二)文件和目录
本章主要介绍的是文件结构及目录.重点是通过stat函数获取文件的结构信息,然后是文件目录及其遍历.学完本章后,编写了一个输出给的目录下的文件信息的程序. 首先是包含在<sys/stat.h> ...
- Unix环境高级编程(一)文件I/O
Unix系统中大多数文件I/O只需用到五个函数:open.read.write.lseek.close.本章说介绍的I/O是不带缓冲的,即:每个read和write都调用内核中的一个系统调用.不是IS ...
- 【UNIX环境高级编程】文件 IO 操作 一 ( open | close | creat | lseek | write | read )
博客地址 : http://blog.csdn.net/shulianghan/article/details/46980271 一. 文件打开关闭操作相关函数介绍 1. open 函数 (1) op ...
- (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- UNIX环境高级编程笔记之文件I/O
一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是“哇”这种很吃惊的表情.其实大概三年前,那会大三,我就买了这本书 ...
- [置顶] 文件和目录(二)--unix环境高级编程读书笔记
在linux中,文件的相关信息都记录在stat这个结构体中,文件长度是记录在stat的st_size成员中.对于普通文件,其长度可以为0,目录的长度一般为1024的倍数,这与linux文件系统中blo ...
随机推荐
- Python标准库:内置函数callable(object)
假设对象object參数是能够调用的对象,就返回True.否则返回False.只是要注意的是,当一个对象是能够调用的.并不表示调用该对象时运行一定成功,但不可调用的对象去调用时一定不会成功.假设类对象 ...
- Shiro 学习应用(续)
在前面的文章中为大家介绍了 Shrio 的基础概念.可能比較笼统.没有深入到开发过程的一些问题.如今集中在本帖中归纳一下有关问题. FormAuthenticationFilter 表单过滤器 表单过 ...
- CodeForcess--609B--The Best Gift(模拟水题)
The Best Gift Time Limit: 2000MS Memory Limit: 262144KB 64bit IO Format: %I64d & %I64u Submi ...
- 剑指offer——05用两个栈实现队列(Python3)
思路:(转) 代码: # -*- coding:utf-8 -*-class Solution: stack1 = [] stack2 = [] def push(self, node): self. ...
- 粘性固定属性 -- position:sticky
概述 position: sticky,这是一个比较容易忽略的css3 position 新属性,它的作用即为实现粘性布局,它是 relative 与 fixed 的结合. 用法 默认情况下,其表现为 ...
- 4. Median of Two Sorted Arrays[H]两个有序数组的中位数
题目 There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the midian of the ...
- ROS集成开发环境RoboWare Studio安装教程
前言:很多人说vim是最快的,所以我选择用roboware. ROS 自带的编辑器vim增加插件,效果如下: RoboWare Studio,效果如下: 下面开始安装. 一.安装 去官网 http:/ ...
- Python学习网络爬虫--转
原文地址:https://github.com/lining0806/PythonSpiderNotes Python学习网络爬虫主要分3个大的版块:抓取,分析,存储 另外,比较常用的爬虫框架Scra ...
- Thingworx 使用REST API获取JSON数据
版本:7.4.1 1.URL规则 http://localhost/Thingworx/Things/[Things名称]/Services/[Service名称]?method=POST&A ...
- 编程范式(Programming Paradigm)-[ 程序员的编程世界观 ]
编程范式(Programming Paradigm)是某种编程语言典型的编程风格或者说是编程方式.随着编程方法学和软件工程研究的深入,特别是OO思想的普及,范式(Paradigm)以及编程范式等术语渐 ...