文件描述符

进程每打开一个文件的时候,会获得该文件的文件描述符,而后续的读写操作都把文件描述符作为参数。在用户空间或者内核空间,都是通过文件描述符来唯一地索引一个打开的文件。文件描述符使用int类型表示,文件描述符的范围从0开始,到上限值-1,默认情况下,上限值为1024,也就是说,进程默认情况下最多可以打开1024个文件。负数是不合法的文件描述符,当函数调用出错时,返回的文件描述符为-1。

每个进程都至少包含三个文件描述符:

文件描述符 表示
0 标准输入(stdin) STDIN_FILENO
1 标准输出(stdout) STDOUT_FINENO
2 标准错误(stderr) STDERR_FILENO

遵循Linux一切皆文件的概念,文件描述符除了访问普通文件外,几乎能够访问任何能够读写的东西。包括设备文件、管道、先进先出缓冲区、套接字等。

open()系统调用

对文件进行读写之前,必须先打开文件。Linux提供了系统调用open()。open()有两个函数原型:


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

两个函数均可用来打开文件,第二个函数比第一个多了参数mode,mode指定文件的权限---当创建新文件的时候才需要。如果文件打开成功,则返回文件描述符,指向pathname所指定的文件。flags参数用于指定打开的方式,它支持三种访问模式:

访问模式 描述
O_RDONLY 打开一个供读取的文件
O_WRONLY 打开一个供写入的文件
O_RDWR 打开一个可供读写的文件

flags参数还可以与下面的值进行按位或运算,修改打开文件的行为:

打开方式 描述
O_APPEND 写入的所有数据将被追加到文件的末尾
O_CREAT 打开文件,如果文件不存在则建立文件
O_EXCL 如果已经设置了O_CREAT且文件存在,则强制open()失败,只能与O_CREAT搭配使用
O_TRUNC 如果文件存在,且是普通文件,并且有写权限,将文件内容清空
O_NONBLOCK 文件以非阻塞模式打开,请见read系统调用

举个例子,下面的句子表示:以写的方式打开文件,如果文件不存在,则创建新的文件,并且文件的内容为空:


int fd ;
fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);

这里的0644指定了新创建的文件访问权限,参数mode的取值如下:

打开方式 描述
S_IRUSR 文件所有者有读权限
S_IWUSR 文件所有者有写权限
S_IXUSR 文件所有者有执行权限
S_IRWXU 文件所有者有读、写、执行权限
S_IRGRP 组用户有读权限
S_IWGRP 组用户有写权限
S_IXGRP 组用户有执行权限
S_IRWXG 组用户有读、写、执行权限
S_IROTH 所有人有读权限
S_IWOTH 所有人有写权限
S_IXOTH 所有人有执行权限
S_IRWXO 所有人有读、写、执行权限

实际上最终写入磁盘的文件访问权限是由mode参数和用户的文件创建掩码(umask)执行按位与操作得到的。举个例子:


int main() {
int fd;
fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRWXG|S_IRWXO );//以只读方式打开文件
//等价于 fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,0777 );//以只读方式打开文件
if(fd == -1)
perror("open file error!");
return 0;
}

按理来说,创建出来的文件的访问权限应该是-rwxrwxrwx,而查看后发现其实不是:

ls -l TEST.txt
-rwxrwxr-x 1 huanzhewu huanzhewu 0 5月 7 21:29 TEST.txt 【权限为0775】

查看当前的掩码:

$ umask
0002

可以发现 0775 = 0777 ^ (~0002) ,所以0775才是最后的文件访问权限。umask是进程级属性,通过调用umask()函数来修改,支持用户修改新创建的文件和目录的权限。

总结起来可以得到这样一条公式:

newmode = mode ^ (~ umask)

总结一下:至此,我们了解了文件打开所提供的两个系统调用函数open(),了解了打开文件的方式、新建文件的访问权限设置。如果文件打开成功,那么将返回一个文件描述符,这是一个非零整数(因为0,1,2是进行已经拥有的文件描述符),否则函数将返回-1

creat()系统调用

顾名思义,creat函数用来创建一个文件,不过我们可能产生疑问:前面的open函数使用一些选项后,不是也可以创建新文件吗?没错,creat函数完全等价与下面的open语句:

int fd ;
fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
fd = creat("file.txt,0644"); /*两个语句的作用完全等价*/

由于选项O_WRONLY|O_CREAT|O_TRUNC组合经常使用,因而系统调用专门使用creat函数来提供这个功能。creat函数的原型如下:

 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);

其中参数的描述与open的参数一致,这里不再赘述。

read()系统调用

文件打开后,就能够读文件了。read()是最基础、最常见的读取文件的机制。read的函数原型为:

 #include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

fd 为文件描述符。每次调用read函数时,会从fd指向的文件的当前偏移(或称文件位置)开始读取count字节到buf所指的的内存中。随着文件的读取,fd的文件位置指针会向前移动。关于read的读取,会出现很多需要思考的问题:

  1. 问题一:如果文件长度为0
  2. 问题二:如果文件长度不够count长度
  3. 问题三:如果读取时,read被信号中断了

我们一一来看:

  1. 问题1属于“没有数据可读”,此时read调用会阻塞,直到有数据可读;
  2. 问题2属于到达数据结尾(EOF),此时read调用返回0
  3. 问题三,read调用返回大于0小于count的值;如果在读取任何数据之前被信号中断,则返回-1,同时把errno设置为EINTR。

由于read有这么多需要考虑的问题,如果希望每次都能读入count个字节,下面是一段示例代码:


//保证读取200个字节到ptr中 ssize_t ret ;
int len = 200;
while(len!= 0 && ( ret = read(fd, ptr, len )) != 0)
{
if(ret == -1)
{
if( errno == EINTR)
continue;
perror("read");
break;
}
len -= ret ;
ptr += ret ;
}

再来看看问题1,当文件没有数据可以读时(一开始就没有),read调用会被阻塞,直到文件有数据可以读,这是一种阻塞I/O。如果文件以O_NONBLOCK模式打开,则文件为非阻塞模式,当文件没有数据可以读时,read系统调用返回-1,并把errno设置为EAGAIN。

        ssize_t ret ;
int len = 200;
while(len!= 0 && ( ret = read(fd, ptr, len )) != 0)
{
if(ret == -1)
{
if( errno == EINTR)
{
printf("读取被中断\n");
continue;
}
if(errno== EAGAIN)
{
printf("文件没有可读\n");
//重新提交读取
continue;
}
break;
}
len -= ret ;
ptr += ret ;
}

除了errno被设置为EINTR与EAGAIN,其他情况下都是出现严重的文件读取错误,重新执行读操作不会成功。

write() 系统调用

write的函数原型为:

    #include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count); 【将buf中count个字节的内容写入fd指定的文件中】

write的返回值比较简单:

  • 写入失败返回-1 ,同时设置errno的值
  • 写入成功返回成功写入的字节数。
  • 返回0时没有特殊含义,仅表示写入了0个字节的内容。

对于普通文件,write基本能保证每次执行调用能够写入全部的内容。对于其他文件如socket,需要进行循环写,保证所有的字节都写入了文件中:

ssize_t ret ;
int len = 200;
while(len!= 0 && ( ret = write(fd, ptr, len )) != 0)
{
if(ret == -1)
{
if( errno == EINTR)
continue;
perror("write");
break;
}
len -= ret ;
ptr += ret ;
}

同样的,当以非阻塞的模式打开文件时(O_NONBLOCK),系统调用write()会返回-1,并把errno设置为EAGAIN。

系统调用write()时,数据从用户空间的缓冲区中拷贝到了内核空间的缓冲区,但并没有立即把数据写入磁盘中,这称为延迟写。延迟写的问题在于,如果在数据真正写入磁盘之前系统崩溃了,则数据可能丢失。内核设置了一个时间,在该时间内将内核空间缓冲区上的数据写入磁盘,该时间称为"最大存放时效"。Linux系统也支持强制文件立即写入磁盘上,这在后面介绍。

close()系统调用

程序完成文件的读写后,调用close函数关闭文件描述符与文件之间的连接,使得文件描述符可以被重用。close的函数原型为:

#incldue<unistd.h>
int close (int fd);

文件关闭成功返回0,出错返回-1,并设置相应的errno。文件成功关闭并不以为着该文件的数据已经被写入磁盘,同步选项在后续介绍。

总结:本文简单介绍了文件的打开、创建、读写、关闭操作,介绍了一些常用的open参数选项,creat与open的等价性,循环读、循环写的必要性,也关注了各个系统调用的返回值含义,了解如何设置非阻塞读写,并简单提到了延迟写的问题,在后续的文件中将介绍同步I/O的内容。

文章连接:http://www.cnblogs.com/QG-whz/p/5469891.html

Linux系统编程:基本I/O系统调用的更多相关文章

  1. linux系统编程之错误处理

    在linux系统编程中,当系统调用出现错误时,有一个整型变量会被设置,这个整型变量就是errno,这个变量的定义在/usr/include/errno.h文件中 #ifndef _ERRNO_H /* ...

  2. Linux系统编程温故知新系列 --- 01

    1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...

  3. LINUX系统编程 由REDIS的持久化机制联想到的子进程退出的相关问题

    19:22:01 2014-08-27 引言: 以前对wait waitpid 以及exit这几个函数只是大致上了解,但是看REDIS的AOF和RDB 2种持久化时 均要处理子进程运行完成退出和父进程 ...

  4. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  5. Linux 系统编程

    简介和主要概念 Linux 系统编程最突出的特点是要求系统程序员对它们工作的的系统的硬件和操作系统有深入和全面的了解,当然它们还有库和系统调用上的区别. 系统编程分为:驱动编程.用户空间编程和网络编程 ...

  6. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  7. Linux系统编程【转】

    转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...

  8. 《Linux系统编程(第2版)》

    <Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...

  9. linux系统编程(一)概述

    glibc库封装了linux系统调用,并提供c语言接口 所以学习linux系统编程,主要参考glibc库系统调用相关api 一.进程控制: fork 创建一个新进程 clone 按指定条件创建子进程 ...

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

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

随机推荐

  1. FunDA(1)- Query Result Row:强类型Query结果行

    FunDA的特点之一是以数据流方式提供逐行数据操作支持.这项功能解决了FRM如Slick数据操作以SQL批次模式为主所产生的问题.为了实现安全高效的数据行操作,我们必须把FRM产生的Query结果集转 ...

  2. 在xcode中用 swift 进行网络服务请求

    xcode集成开发环境是运行于Mac苹果电脑上用于开发swift应用程序的工具,利用xcode可以很方便.直观的开发OS X和iOS系统所支持的应用程序. 1 开发环境: Mac OS 10.11 X ...

  3. sencha ext js 6 入门

    Sencha Ext JS号称是目前世界上最先进和最强大的.支持多平台多设备的JavaScript应用程序开发框架.首先看一下Ext JS的发展简史. 1 Ext JS发展简史 YUI-Ext的作者J ...

  4. 五个典型的 JavaScript 面试题

    阅读原文 在IT界,需要大量的 JavaScript 开发者.如果你的能力能够胜任这一角色,那么你将获得许多更换工作和提高薪水的机会.但是在你被公司录取之前,你需要展现你的技术实力,以便通过面试环节. ...

  5. node.js xtemplate的使用实例

    工程下安装XTemplate并使用它的方法实例说明: 1.安装xtpl npm install xtpl xtemplate --save 2.在views目录添加test.xtpl文件,其内容为 t ...

  6. React Native 之生命周期

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  7. uboot的配置流程分析

    简单介绍一下uboot的基本配置流程.和绝大多数源码编译安装一样,uboot在执行make之前需要执行make XXXconfig来配置相关信息,而且uboot本身是针对多种平台的bootloader ...

  8. Ubuntu 下ibus拼音输入法启用 (ubuntu 16.04

    Ubuntu 下ibus拼音输入法启用 我安装的是英文版的ubuntu 16.04,打开只带英文,并没有中文. 设置输入法为iBus 从system settings 进入language suppo ...

  9. iOS开发常用代码块(第二弹)

    GCD定时器 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); dispat ...

  10. AC日记——二叉堆练习3 codevs 3110

    3110 二叉堆练习3  时间限制: 3 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description 给定N(N≤500,000)和N个整 ...