linux文件io(open、read、write、close)

2018-05-19 12:42:19

所有执行io操作的系统调用都是以文件描述符(大于0的整数)来指代打开的文件。文件描述符可以表示诸如管道(pipe)、fifo、socket、终端、设备和普通文件。对于每个进程,文件描述符都自成一套。
下图的3个文件描述默认都会打开(可以说都是继承自shell文件描述符的一个副本)

 文件描述符用途  posix 名称stdio流 
 0 标准输入 STDIN_FILENO stdin
 1 标准输出 STDOUT_FILENO stdout
 2 标准错误 STDERR_FILENO stderr

程序中可以用0、1、2来代表文件描述符也可以用<unistd.h>里的STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO来表示。

下面介绍io操作主要的4个系统调用。

int open(const char *pathname, int flags, /* mode_t mode */);
打开文件并返回文件描述符,flags是掩码参数(下图),mode是权限相关(S_IRUSR | S_IWUSR | S_IXUSR      S_IRGRP | S_IWGRP | S_IXGRP     S_IROTH | S_IWOTH | S_IXOTH)

ssize_t read(int fd, void *buffer, size_t count);
读取最多count个字节到buffer中,返回读取到的字节数,如果独到文件末尾则返回0,出错返回-1。为啥说是最多呢,因为对于普通文件而言,有可能接近了文件结尾,对于其他如socket,fifo等就有其他复杂的因素。

ssize_t write(int fd, void *buffer, size_t count);
从buffer写入最多count个字节到文件中,fd是指代要写入的文件描述符。如果写入成功返回写入的字节数。返回值可能小于count造成部分写,有可能是磁盘满了,也有可能是进程资源的文件大小的限制。

int close(int fd);
关闭打开的文件描述符

off_t lseek(int fd, off_t offset, int whence);
改变文件偏移量

off_t curr = lseek(fd, 0, SEEK_CUR);  //获取当前offset
lseek(fd, 0, SEEK_SET); /*定位到文件的开始*/
lseek(fd, 0, SEEK_END); /*定位到文件结尾的下一个字节*/
lseek(fd, -1, SEEK_END); /* 定位最后一个字节*/
lseek(fd, -10, SEEK_CUR); /* 当前往回10字节 */
lseek(fd, 10000, SEEK_END); /* 距离结尾文件 10001 字节*/

int fcntl(int fd, int cmd, ...);
文件控制操作

//设置非阻塞
int flags = fcntl(fd, F_GETFL);    //获取当前的标志
flags = flags | O_NONBLOCK;    //追加
fcntl(fd, F_SETFL, flags);     //重新设置

//检测文件标志
if (flags & O_SYNC)
    printf("writes are synchronized\n");

//检测权限-略复杂
int accessMode = flags & O_ACCMODE;
if (accessMode == O_WRONLY || accessMode == O_RDWR)
    printf("file is writable\n");


ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

原子操作,可以手动指定(SEEK_SET)在offset偏移量来读取和写入操作,操作完后会复原当前的偏移量


文件描述符与打开文件

多个文件描述符可以指向同一个打开文件,同一个文件描述符可以指向多个打开文件。内核有3个数据结构。

1.进程级别的文件描述符表
2.系统级别的打开文件表
3.文件系统的i-node表

我们解释下上图,第1列一条数据代表一个文件描述符,第2列一条数据代表依次打开文件比如open,第3列一条数据代表一个文件(这里只代表普通文件)。
进程A的fd1,fd20是2个文件描述符,但是它们指向同一个打开文件23,fd20可能是通过dup,dup2,fcntl复制出来的。
进程A的fd2和进程B的fd2指向同一个打开文件73,则可能是进程B是进程A的子进程,或者是某进程通过unix域套接字将一个打开文件描述符传递个另一个进程。
进程A的fd0指向打开文件0,进程B的fd3指向打开文件86,单却指向同一个inode1976,换言之指向同一个文件,那是因为进程A和进程B都打开了同一文件,同一个进程调用2次open同一个文件也能发生类似情况。

总结:从上可以得出结论除了close-on-exec标志位每个进程的文件描述符所私有,其他的有可能会共用。比如进程A在fd1上修改文件偏移后会保存在打开文件23,那么fd20也会受到影响。


复制文件描述符

int dup(int oldfd);
复制文件描述符oldfd并返回一个新的文件描述符,2个文件描述符指向同一个打开文件,新文件描述符会取整数且最小未使用的。

int dup2(int oldfd, int newfd);
复制文件描述符oldfd并返回一个新的文件描述符 ,编号由newfd决定,如果newfd已经打开,则会先关闭newfd(由于dup2会忽略关闭newfd的错误,所以更为安全的做法是用close手动关闭newfd)。 

int newfd = fcntl(oldfd, F_DUPFD, startfd);
这可以用来复制,文件描述符编号会取大于等于startfd的最小未使用的整数。

int newfd = fcntl(oldfd, F_DUPFD_CLOEXEC, startfd);
这可以用来复制,文件描述符编号会取大于等于startfd的最小未使用的整数并在新的文件描述符上设置close-on-exec标志。  

int dup3(int oldfd, int newfd, int flags);
能实现上面同样的功能,在新的文件描述符上加上close-on-exec标志。


截断文件

int truncate(const char *pathname, off_t length);
对文件必须要有写的权限

int ftruncate(int fd, off_t length);
必须以写的方式先打开文件,不会改变文件的偏移量

/dev/fd

这个目录是一个软连接指向/proc/self,/proc/self也是一个软连接,它指向另一个目录,假设我们当前的进程号为6674,那么实际指向/proc/6674/fd。这个目录里有当前进程所有打开的文件描述符。可以利用这一特性来访问自己已打开的文件描述符,这一特性在shell中尤其有用。

创建临时文件

int mkstemp(char *template);
返回文件描述符,必须手动删除
FILE *tmpfile(void);
打开后自动删除但是文件依然存在于内存,可以操作,在linux如果一个文件被打开了,我们是可以手动删除这个文件的,但是这是只是删除了该文件名,内存中还是保留着该文件,可以对这个文件执行读写操作,当程序结束时,才算真的删除。基于这个特性,如果只是想生成临时文件,打开后就可立马删除,但是后续还可以操作该文件。

//例子
int fd;
char template[] = "/tmp/tmp.XXXXXX";
fd = mkstemp(template);                 //生成临时文件并打开 template=/tmp/tmp.CYDaN7
unlink(template);                       //手动删除

FILE *fp = tmpfile();                   //创建后会自动删除
fclose(fp);                             //自动删除


总结

1.对文件执行追加操作是原子操作,所以当多个进程以追加方式同时打开一个文件不会出现竞争危险。2.本文对linux文件io做了简单的介绍,因为io是编程的基本,所以放在比较靠前来讲,如果有疑问可以给我留言。

备注

1.gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/2a


©著作权归作者所有
收藏
推荐阅读
  • err
    linux系统调用及错误处理

    系统调用是内核提供给外部程序的接口,进程可以通过系统调用来一自己的名义来执行某些动作。在深入了解系统调用之前,先关注以下几点。1.系统调用处理器会从用户态切换到核心态以便...

  • linux内核简介

    内核是用来管理和分配计算机资源的,它主要有进程调度、内存管理、提供文件系统、创建和终止进程、对设备的访问、联网、提供系统调用的接口等。内核还可以为内阁用户模拟出抽象的虚拟私有计算机,每个用户都可以登录...

  • linux centos7 安装逻辑卷指令来支持lvm2

    报如下错时。-bash: pvcreate: command not found-bash: pvscan: command not found-bash: pvdisplay: command no...

  • linux命令系列-fuser,lsof,pidof

    fuser 可以借由文件(或文件系统) 找出正在使用该文件的程序。有的时候我想要知道我的程序到底在这次启动过程中打开了多少文件,可以利用 fuser 来观察啦! 举例来说,你如果卸载时发现系统通知:“...

  • err
    linux利用ssh-keygen实现免密码登录

    思考下以下几个问题:利用crontab定时任务半夜利用sftp或rsync来自动链接远程来备份文件利用xshell或linux之间登录时无需重复输入密码即可直接登录我们知...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。