内核通过文件描述符引用打开的文件,通常通过open函数或者create函数返回文件描述符。

基本函数:

unix中基础的文件操作函数只有5个,分别是open,close,lseek,read,write,这几个函数都是无缓冲的,意味着这些函数的执行都是在内核中完成的。

open函数:

#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */ );
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );

只有创建文件时,才需要指定最后一个参数,通过最后一个参数指定创建的文件的访问权限。oflag参数有很多选项,下面五个是必须指定且只能指定其中一个:O_RDONLY,O_WRONLY,

O_RDWR,O_SERCH,O_EXEC,其他的如:O_CREAT,O_APPEND,O_CLOEXEC,O_EXCL,O_DIRECTORY,O_NOFOLLOW,O_NOBLOCK,O_SYNC,O_TRUNC是可选的,可以通过OR操作与上面五个组合。

openat函数是为了在多线程环境下,同一个进程内不同线程可以通过相对路径打开非当前进程工作目录的文件。第二个目的就是为了避免 TOCTTOU错误。

O_CLOEXEC关闭了fd标志,在通过exec执行新程序的时候,在新的进程中,当前fd是被关闭的,否则是仍可以访问的,而且通过这种方式关闭新的文件描述符的close-exec是原子操作,因为在多线程环境下通过fcntl的F_SEDFD关闭描述符的close-exec会有竞争条件。当一个线程通过fcntl函数指定F_SETFD,而另为的线程fork再exec就出现了竞争。

同时制定了O_CREAT和O_EXCL可以原子性的测试文件是否存在,如果文件已经存在open调用将会失败。

同时指定O_CREAT|O_WRONLY|O_TRUNC相当于调用creat函数,而且是原子操作。

close函数:

#include <unistd.h>

       int close(int fd);

关闭文件描述符的时候同时会释放施加在该描述符对应的文件上的记录锁。当进程结束是,内核会自动关闭该进程打开的文件。

lseek函数:

#include <sys/types.h>
#include <unistd.h> off_t lseek(int fd, off_t offset, int whence);

成功返回:offset,失败返回-1。

where的取值为:SEEK_SET,SEEK_CUR,SEEK_END分别代表文件头,当前位置,文件尾部。lseek的返回值根据where的值和offset而不同,当where的值为SEEK_CUR,SEEK_END时候,返回值可正可负,所以判断函数是否失败不可用仅仅测试返回值是否小于0.

通过lseek(int fd,0,SEEK_CUR)可以获取当前文件偏移量。

lseek本身不会引起任何IO读写。

lseek可以设置偏移量off_t超过文件尾,但是不会增加文件大小,如果这时候在这个位置写数据,在文件尾和新的数据之间,读取出来的是空洞’\0’,直到空洞被填满。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h> #define PATH "hole"
#define BUFFER "12345"
int main()
{
ssize_t size;
off_t offt;
int fd = creat(PATH,S_IRWXU);
if( fd < )
{
printf("creat %s faild\n",PATH);
return -;
} size = write(fd,BUFFER,sizeof(BUFFER));
if( size != sizeof(BUFFER))
{
printf("write faild\n");
return -;
}
offt = lseek(fd,,SEEK_CUR);
if( - == offt )
{
printf("seek failed\n");
return -;
} size = write(fd,BUFFER,sizeof(BUFFER));
if( size != sizeof(BUFFER))
{
printf("write faild\n");
return -;
}
return ;
}

在32位系统如果想使用64位的off_t需要指定_FILE_OFFSET_BITS=64,在64位上使用32位的off_t需要指定_FILE_OFFSET_BITS=32,可以在CFLAG中使用-D指定这个_FILE_OFFSET_BITS=64或者32,例如:CFLAG=-D_FILE_OFFSET_BITS=64

read函数:

#include <unistd.h>

 ssize_t read(int fd, void *buf, size_t count);

返回-1表示失败,返回值可能小于count,因为可能达到了文件尾,或者文件是FIFO,终端设备,或者读操作被信号中断。

write函数:

#include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

返回-1表示失败,返回值可能小于count,因为设备可能没有空间了,可能达到了RLIMIT_FSIZE限制,或者写操作被信号中断。对于可以seek的文件,如果打开文件的时候没有指定O_APPEND标志,每次成功的写都会增加offset。如果打开时候指定了O_APPEND,那么每次写的时候当前offset都会设置为文件尾,也就是当前文件大小。注意seek和write在这种情况下是原子操作。

关于读写的buffer大小,一般选择4096是效率比较好的。

Single UNIX Specification 中提供了两个函数允许seek和read,write为一个原子操作的函数:

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
Returns: number of bytes read, if end of file, − on error
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
Returns: number of bytes written if OK, − on error

文件共享

每个进程在进程表中占一个进程表项,进程表项中包含FD表,与FD关联的是fd标志和指向文件表的指针

每个打开的文件在文件表中占一个表项,与之关联的是文件状态,文件当前偏移量以及指向V节点的指针

每个打开的文件都有一个V节点结构,V节点中包含I节点,文件大小,文件在磁盘上存储等。

下图是两个进程打开同一个文件:

之所以同一个文件有两个文件表项,是因为这样每个打开它的进程都有自己的当前文件偏移量。

dup函数:

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);

失败返回-1,成功返回新的fd

dup执行成功一定返回最小可用fd。执行dup后进程和文件表之间的示意关系。

dup2可以通过fd2指定新的fd,如果哦d2已经打开,先关闭它,如果fd和fd2相同,则不关闭它。dup2会清除新的fd的FD_CLOEXEC标记,所以进程执行exec后,这个fd仍然可以使用。

数据刷新函数:

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
Returns: if OK, − on error
void sync(void);

sync函数仅仅刷新缓存队列,并不等待IO操作完成,Linux下没有此函数。

fsync冲洗fd对应的文件队列数据,包括文件属性,等待IO操作完成,适合在需要实时保存数据的情景下使用。

fdatasyn同fsync但是只刷新数据。

fcntl函数:

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */ );
Returns: depends on cmd if OK (see following), − on error

随着cmd的取值不同,fcntl的功能也不一样:

F_DUPFD:复制fd到大于等于arg的最小可用描述符并返回这个描述符;

F_DUPFD_CLOEXEC:同上但是新描述符的FD_CLOEXEC标志被关闭;

F_GETFD:获取fd标记,目前只有一个标志FD_CLOEXEC

F_SETFD:设置fd标记

F_GETFL:获取文件状态字和访问模式

F_SETFL:设置文件状态字和访问模式

fcntl 函数指定F_GETFD和F_SETFD时,不可以仅仅设置值,因该先取得原先值然后打开对应的bit位再设置新的值。下面的函数打开相应的位:

#include "apue.h"
#include <fcntl.h>
void
set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
int val;
if ((val = fcntl(fd, F_GETFL, )) < )
err_sys("fcntl F_GETFL error");
val |= flags; /* turn on flags */
if (fcntl(fd, F_SETFL, val) < )
err_sys("fcntl F_SETFL error");
}

下面的函数关闭相应的位:

#include "apue.h"
#include <fcntl.h>
void
set_fl(int fd, int flags) /* flags are file status flags to turn off */
{
int val;
if ((val = fcntl(fd, F_GETFL, )) < )
err_sys("fcntl F_GETFL error");
val &= ~flags; /* turn off flags */
if (fcntl(fd, F_SETFL, val) < )
err_sys("fcntl F_SETFL error");
}

O_RDONLY, O_WRONLY,O_RDWR, O_EXEC, and O_SEARCH这几个访问模式不是单独可测试的位,应该用O_ACCMODE获取相应的位然后再做比较:

int val = fcntl(fd,F_GETFL,);
switch( val & O_ACCMODE)
{
case O_RDONLY:
printf("read only\n");
break;
case O_WRONLY:
printf("write only\n");
break;
case O_RWONLY:
printf("read write only\n");
break;
default:
printf("unkonwn \n"); }

/dev/fd 文件:

使用下面代码相当于dup描述符n,假定描述符n是打开的且忽略mode参数。

open("/dev/fd/n",mode)

Unix 基础IO的更多相关文章

  1. 《UNIX环境高级编程》(APUE) 笔记第一章 - UNIX基础知识

    1 - UNIX基础知识 Github 地址 1. 操作系统 可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境.通常将这种软件称为 内核 (kernel) .( Linux 是 GN ...

  2. C 标准库基础 IO 操作总结

    其实输入与输出对于不管什么系统的设计都是异常重要的,比如设计 C 接口函数,首先要设计好输入参数.输出参数和返回值,接下来才能开始设计具体的实现过程.C 语言标准库提供的接口功能很有限,不像 Pyth ...

  3. Java基础IO流(二)字节流小案例

    JAVA基础IO流(一)https://www.cnblogs.com/deepSleeping/p/9693601.html ①读取指定文件内容,按照16进制输出到控制台 其中,Integer.to ...

  4. UNIX环境高级编程--第一章 UNIX基础知识

    第一章 UNIX基础知识 1.2 UNIX体系结构   从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境.我们将这种软件称为内核(kernel),因为 它相对较小,且 ...

  5. Java基础-IO流对象之压缩流(ZipOutputStream)与解压缩流(ZipInputStream)

    Java基础-IO流对象之压缩流(ZipOutputStream)与解压缩流(ZipInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 之前我已经分享过很多的J ...

  6. Java基础-IO流对象之随机访问文件(RandomAccessFile)

    Java基础-IO流对象之随机访问文件(RandomAccessFile) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.RandomAccessFile简介 此类的实例支持对 ...

  7. Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)

    Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存 ...

  8. Java基础-IO流对象之数据流(DataOutputStream与DataInputStream)

    Java基础-IO流对象之数据流(DataOutputStream与DataInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数据流特点 操作基本数据类型 ...

  9. Java基础-IO流对象之打印流(PrintStream与PrintWriter)

    Java基础-IO流对象之打印流(PrintStream与PrintWriter) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.打印流的特性 打印对象有两个,即字节打印流(P ...

随机推荐

  1. 一个 XSD 实例

    一个 XSD 实例 本节会为您演示如何编写一个 XML Schema.您还将学习到编写 schema 的不同方法. XML 文档 让我们看看这个名为 "shiporder.xml" ...

  2. cf 357C

    比赛的时候纯暴力超时了  看了别人的代码  set容器类做的   stl里还是有很多好东西的 /**************************************************** ...

  3. WP-Syntax 插件使用方法

    技术博客中使用WP-Syntax将代码高亮是最常见的.而一段时间不用总会忘记每种语言的的pre标签的值. 这里简单介绍下,WP-Syntax 是一个针对 Wordpress 的代码高亮插件,最大的优点 ...

  4. PowerDesigner15(16)在生成SQL时报错Generation aborted due to errors detected during the verification of the mod

    1.用PowerDesigner15建模,在Database—>Generate Database (或者用Ctrl+G快捷键)来生产sql语句,却提示“Generation aborted d ...

  5. Let's go! (Ubuntu下搭建Go语言环境)

    自2009年Go语言发布以来,我一直在关注Go语言,如今Go语言已经发展到1.2版本,而且也收到越来越多的人关注这门语言.Go语言设计的目的就是为了解决执行数度快但是编译数度并不理想(如C++)以及编 ...

  6. javaweb学习总结(四十五)——监听器(Listener)学习二

    一.监听域对象中属性的变更的监听器 域对象中属性的变更的事件监听器就是用来监听 ServletContext, HttpSession, HttpServletRequest 这三个对象中的属性变更信 ...

  7. XST综合、实现过程包含哪些步骤

    2013-06-25 18:53:50 在ISE的主界面的处理子窗口的synthesis的工具可以完成下面的任务: 查看RTL原理图(View RTL schematic) 查看技术原理图(View ...

  8. 学Android开发 这19个开发工具助你顺风顺水

    学Android开发 这19个开发工具助你顺风顺水 要想快速开发一个Android应用,通常会用到很多工具,巧妙利用这些工具,能让我们的开发工作事半功倍,节省大量时间,下面大连Android开发培训小 ...

  9. JAVA高级特性 - 注解

    注解是插入到代码中用于某种工具处理的标签.这些标签可以在源码层次上进行操作,或者可以处理编译器将其纳入到注解类文件中. 注解不会改变对程序的编译方式.Java编译器会对包含注解和不包含注解的代码生成相 ...

  10. POJ 3204 Ikki's Story I-Road Reconstruction (网络流关键边)

    [题意]给定一个N个节点M条边的网络流,求有多少条边,使得当增其中加任何一个边的容量后,整个网络的流将增加. 挺好的一道题,考察对网络流和增广路的理解. [思路] 首先关键边一定是满流边.那么对于一个 ...