Linux系统编程之文件I/O函数的使用:介绍文件I/O函数的基本概念、用法和实现方式

  • 概述

不带缓冲的I/O(内核缓冲)调用内核中系统调用

内核而言,所有打开的文件都通过文件描述符引用。

低级I/O

不同于标准io man 2 不使用 FILE * 结构体

而使用文件描述符 0~1023的整数值(非负整数)

#define stdin 0
#define stdout 1
#define stderr 2

  • 文件描述符

文件描述符fd是Linux相对有限的资源,单个进程中的fd数量有限制,一般默认是1024。

查看当前session的fd数量限制
# ulimit -n

修改当前session的fd数量限制,注意只对当前session有效

# ulimit -n your_need

  • 同一个进程的不同文件描述符可以指向同一个文件;
  • 不同进程可以拥有相同的文件描述符;
  • 不同进程的同一个文件描述符可以指向不同的文件(一般也是这样,除了 0、1、2 这三个特殊的文件);
  • 不同进程的不同文件描述符也可以指向同一个文件。

在使用文件描述符的时候,会从最小的未使用的值开始再分配,

文件描述符的上限 1024个是指当前ubuntu 默认设置,可以自己修改上限


  • 文件io函数

  • open/create(打开或创建一个文件)

#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);
int opent(int dirfd ,const char *pathname ,int flags,. . ./* mode_t mode */ );//可以使用相对路径打开文件

​
int creat(const char *pathname,mode_t mode);
//等价于open(pathname,O_CREAT | O_TRUNC | O_WRONLY,mode);

参数

pathname 指向欲打开的文件路径字符串. 下列是参数flags 所能使用的旗标:

O_RDONLY      以只读方式打开文件

O_WRONLY     以只写方式打开文件

O_RDWR         以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合

//3个中必须且只能选一个

O_CREAT               若欲打开的文件不存在则自动建立该文件.

//需要mode  权限  但不是最终生成的文件权限

//  需要   mode & (~umask)

O_EXCL                  如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.

O_TRUNC             若文件存在并且以可写的方式打开时, 此标志会将文件长度清为0, 而原来存于该文件的资料也会消失.

O_APPEND           当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.

O_NOCTTY          如果欲打开的文件为终端设备时, 则不会将该终端机当成进程控制终端机.

O_NOFOLLOW    如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.

O_DIRECTORY     如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注:此为Linux2. 2 以后特有的旗标, 以避免一些系统安全问题.

O_NONBLOCK/O_NDELAY      以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.

O_NOATIME       当文件被读取时,不更新文件最后访问时间 (自Linux 2.6.8起)

O_SYNC              以同步步I / O的方式打开文件,所得文件描述符上的任何write都将阻止 调用进程,直到数据被物理地写入底层硬件。 

O_CREAT|O_EXCL     // 固定搭配 不存在创建,存在出错 保护文件

O_TRUNC|O_CREAT    // 存在则覆盖,不存在则创建 方便调试 

参数mode 则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).

 S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.

S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.

S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.

S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.

S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.

S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.

S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.

S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.

S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.

S_IROTH 00004 权限, 代表其他用户具有可读的权限

S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.

S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.

返回值:

若所有欲核查的权限都通过了检查则返回0 值, 表示成功, 只要有一个权限被禁止则返回-1.

错误代码:

EEXIST 参数pathname 所指的文件已存在, 却使用了O_CREAT 和O_EXCL 旗标.

EACCESS 参数pathname 所指的文件不符合所要求测试的权限.

EROFS 欲测试写入权限的文件存在于只读文件系统内.

EFAULT 参数pathname 指针超出可存取内存空间.

EINVAL 参数mode 不正确.

ENAMETOOLONG 参数 pathname 太长.

ENOTDIR 参数pathname 不是目录.

ENOMEM 核心内存不足.

ELOOP 参数pathname 有过多符号连接问题.

EIO I/O 存取错误.


  • umask()(设置建立新文件时的权限)

此函数的主要作用是在创建文件时设置或者屏蔽掉文件的一些权限。一般与open()函数配合使用。1

#include<sys/stat.h>
//定义函数: 
mode_t umask(mode_t mask);
//函数说明: umask()会将系统umask值设成参数mask&0777后的值,然后将先前的umask值返回。

在使用open()建立新文件时,该参数 mode并非真正建立文件的权限,而是(mode& ~umask)的权限值。

例如,在建立文件时指定文件权限为0666,通常umask值默认为 022,则该文件的真正权限则为0666&~022=0644,

也就是rw-r--r--返回值此调用不会有错误值返回。

返回值为原先系统的umask值。

其中,cmask 是由下表列出的9个常量中的若干个按位”或”构成的

- S_IRUSR 当前用户可读

- S_IWUSR 当前用户可写

- S_IXUSR 当前用户可执行

- S_IRGRP 用户组可读

- S_IWGRP 用户组可写

- S_IXGRP 用户组可执行

- S_IROTH 其他用户可读

- S_IWOTH 其他用户可写

- S_IXOTH 其他用户可执行

  • close(关闭一个文件)

     int close(int fd);

     返回值:

     成功返回0,失败返回-1

     参数:

     fd:已打开的文件描述符


  • lseek(为打开文件设置偏移量)

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

​
可以用如下所示确定当前文件的偏移量,同时可以检测文件是否可以设置偏移量:
      off_t currpos;
      currpos=lseek(fd,0,SEEK_CUR)
返回值:
当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回-1,errno 会存放错误代码。
fd为已打开的文件描述词
参数fildes 为已打开的文件描述符,参数offset 为根据参数whence来移动读写位置的位移数。
whence为下列其中一种:(SEEK_SET,SEEK_CUR和SEEK_END和依次为0,
SEEK_SET 将读写位置指向文件头后再增加offset个位移量。
SEEK_CUR 以目前的读写位置往后增加offset个位移量。
SEEK_END 将读写位置指向文件尾后再增加offset个位移量。
当whence 值为SEEK_CUR 或SEEK_END时,参数offet允许负值的出现。
下列是较特别的使用方式:
1)欲将读写位置移到文件开头时:lseek(int  fildes, 0, SEEK_SET)
2)欲将读写位置移到文件尾时时:lseek(int fildes, 0, SEEK_END)
3)欲将取得目前文件位置时:       lseek(int fildes, 0, SEEK_CUR)

  • read(从打开文件中读数据)

     

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

     返回值:

     成功返回读到的字节数,若到达文件末尾返回0,出错返回-1。

     参数:

     buf:缓冲区存放读取的数据

     count:请求读取的字节数


  • write(向打开文件写数据)

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

     返回值:

     成功返回已写的字节数,出错返回-1。

  • dup/dup2(复制一个现有的文件描述符)

     int dup (int oldfd);

功能

通过 oldfd 复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终 oldfd 和新的文件描述符都指向同一个文件。

参数

oldfd: 需要复制的文件描述符 oldfd

返回值

成功:新文件描述符

失败:-1


int dup2(int oldfd, int newfd);

功能:

通过 oldfd 复制出一个新的文件描述符 newfd,如果成功,newfd 和函数返回值是同一个返回值,最终 oldfd 和新的文件描述符 newfd 都指向同一个文件。

参数:

oldfd: 需要复制的文件描述符

newfd: 新的文件描述符,这个描述符可以人为指定一个合法数字(0-1023),如果指定的数子已经被占用(和某个文件有关联),此函数会自动关闭 close() 断开这个数字和某个文件的关联,再来使用这个合法数字。

返回值:

成功:返回 newfd

  • fsync/fdatasync/msync(同步内存中所有已修改的文件数据到储存设备)

 #include <unistd.h>   
  int fsync (int fd);

  int fdatasync (int fd);//只影响数据部分,不更新文件属性   

#incude <sys/mman.h>

  int msync(void *addr, size_t length, int flags)

fsync的功能是确保文件fd所有已修改的内容已经正确同步到硬盘上,该调用会阻塞等待直到设备报告IO完成。

fdatasync的功能与fsync类似,但是仅仅在必要的情况下才会同步metadata,因此可以减少一次IO写操作。那么,什么是“必要的情况”呢?根据man page中的解释:

"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."

举例来说,文件的尺寸(st_size)如果变化,是需要立即同步的,否则OS一旦崩溃,即使文件的数据部分已同步,由于metadata没有同步,依然读不到修改的内容。而最后访问时间(atime)/修改时间(mtime)是不需要每次都同步的,只要应用程序对这两个时间戳没有苛刻的要求,基本无伤大雅。

msync需要指定同步的地址区间,如此细粒度的控制似乎比fsync更加高效(因为应用程序通常知道自己的脏页位置),但实际上(Linux)kernel中有着十分高效的数据结构,能够很快地找出文件的脏页,使得fsync只会同步文件的修改内容。

PS:如果采用内存映射文件的方式进行文件IO(使用mmap,将文件的page cache直接映射到进程的地址空间,通过写内存的方式修改文件),也有类似的系统调用来确保修改的内容完全同步到硬盘之上:

   返回值:

     成功返回0 ,出错返回-1

fflush和fsync的联系和区别

1.

提供者

fflush是libc.a中提供的方法,

fsync是系统提供的系统调用。

2.

原形

fflush接受一个参数FILE *.fflush(FILE *);

fsync接受的时一个Int型的文件描述符。fsync(int fd);

3.功能

fflush:是把C库中的缓冲调用write函数写到磁盘[其实是写到内核的缓冲区]。

fsync:是把内核缓冲刷到磁盘上。 

c库缓冲-----fflush---------〉内核缓冲--------fsync-----〉磁盘

fsync的性能问题,与fdatasync

除了同步文件的修改内容(脏页),fsync还会同步文件的描述信息(metadata,包括size、访问时间st_atime & st_mtime等等),因为文件的数据和metadata通常存在硬盘的不同地方,因此fsync至少需要两次IO写操作,fsync的man page这样说:

"Unfortunately fsync() will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept fdatasync() can be used to avoid unnecessary inode disk write operations."

多余的一次IO操作,有多么昂贵呢?根据Wikipedia的数据,当前硬盘驱动的平均寻道时间(Average seek time)大约是3~15ms,7200RPM硬盘的平均旋转延迟(Average rotational latency)大约为4ms,因此一次IO操作的耗时大约为10ms左右。

使用fdatasync优化日志同步

为了满足事务要求,数据库的日志文件是常常需要同步IO的。由于需要同步等待硬盘IO完成,所以事务的提交操作常常十分耗时,成为性能的瓶颈。

在Berkeley DB下,如果开启了AUTO_COMMIT(所有独立的写操作自动具有事务语义)并使用默认的同步级别(日志完全同步到硬盘才返回),写一条记录的耗时大约为5~10ms级别,基本和一次IO操作(10ms)的耗时相同。

 我们已经知道,在同步上fsync是低效的。但是如果需要使用fdatasync减少对metadata的更新,则需要确保文件的尺寸在write前后没有发生变化。日志文件天生是追加型(append-only)的,总是在不断增大,似乎很难利用好fdatasync。

且看Berkeley DB是怎样处理日志文件的:

1.每个log文件固定为10MB大小,从1开始编号,名称格式为“log.%010d"

2.每次log文件创建时,先写文件的最后1个page,将log文件扩展为10MB大小

3.向log文件中追加记录时,由于文件的尺寸不发生变化,使用fdatasync可以大大优化写log的效率

4.如果一个log文件写满了,则新建一个log文件,也只有一次同步metadata的开销

  • fcntl(根据文件描述词来操作文件的特性)

  

#include <fcntl.h>;
 
    int fcntl(int fd, int cmd); 
    int fcntl(int fd, int cmd, long arg); 
  

Fcntl()针对(文件)描述符提供控制.

 参数

fd: 是被参数cmd操作(如下面的描述)的描述符.

cmd的取值可以如下:

        F_DUPFD            

返回一个如下描述的(文件)描述符:

    o            最小的大于或等于arg的一个可用的描述符

    o            与原始操作符一样的某对象的引用

    o  如果对象是文件(file)的话,返回一个新的描述符,这个描述符与arg 共享相同的偏移量(offset)

    o            相同的访问模式(读,写或读/写)

    o            相同的文件状态标志(如:两个文件描述符共享相同的状态标志)

    o            与新的文件描述符结合在一起的close-on-exec 标志被设置成交叉式访问execve(2)的系统调用   

        F_GETFD            

取得与文件描述符fd联合close-on-exec标志,类似FD_CLOEXEC.如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保 持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)   

        F_SETFD            设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。     

        F_GETFL            取得fd的文件状态标志,如同下面的描述一样(arg被忽略

        F_SETFL            设置给arg描述符状态标志,可以更改的几个标志是: O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC

        F_GETOWN             取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)

        F_SETOWN            设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id

     命令字(cmd)F_GETFL和F_SETFL的标志如下面的描述:

            O_NONBLOCK            非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误

            O_APPEND                    强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志

            O_DIRECT                    最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.如果不能够避免缓存,那么它将最小化已经被缓存了的数据造 成的影响.如果这个标志用的不够好,将大大的降低性能

            O_ASYNC                    当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

获得/设置记录锁的功能: (cmd=F_GETLK,F_SETLK或F_SETLKW).

         F_GETLK          

通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的的锁.

取得的信息将覆盖传到fcntl()的flock结构的信息.如果没有发现能够阻止本次锁(flock)生成的锁,

这个结构将不被改变,除非锁的类型被设置成F_UNLCK.

        F_SETLK          

按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件segment锁.

F_SETLK被用来实现共享(或读)锁 (F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK).

如果共享锁或独占锁不能被设置,fcntl()将立即返 回EAGAIN.

        F_SETLKW          

除了共享锁或独占锁被其他的锁阻塞这种情况外,这个命令和F_SETLK是一样的.

如果共享锁或独占锁被其他的锁阻塞,进程将等待直到这个请求能够完成. 

当fcntl()正在等待文件的某个区域的时候捕捉到一个信号,如果这个信号没有被指定SA_RESTART,fcntl将被中断.

          当一个共享锁被set到一个文件的某段的时候,其他的进程可以set 共享锁到这个段或这个段的一部分.共享所阻止任何其他进程set独占锁到这段保护区域的任何部分.如果文件描述符没有以读的访问方式打开的话,共享锁的设置请求会失

          独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁.如果文件描述符不是以写的访问方式打开的话,独占锁的请求会失败

结构体flock的指针 :

struct flcok 
{ 
short int l_type; /* 锁定的状态*/
//这三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET,l_start=0,l_len=0;
short int l_whence;/*决定l_start位置*/ 
off_t l_start; /*锁定区域的开头位置*/ 
off_t l_len; /*锁定区域的大小/长度*/

pid_t l_pid; /*进程的ID,持有的锁能阻塞当前进程*/ 
};
/*
l_type 有三种状态: 
F_RDLCK 建立一个供读取用的锁定 ,共享读锁    
F_WRLCK 建立一个供写入用的锁定 ,独占性写锁
F_UNLCK 删除之前建立的锁定,解锁一个区域

l_whence 也有三种方式: 
SEEK_SET 以文件开头为锁定的起始位置。 
SEEK_CUR 以目前文件读写位置为锁定的起始位置 
SEEK_END 以文件结尾为锁定的起始位置。 
*/

fcntl的返回值 :

与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。

下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。

第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

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).

  • ioctl(对设备的I/O通道进行管理)

      

int ioctl(int fd, ind cmd, …);

 ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

参数:

fd:用户程序打开设备时使用open函数返回的文件标示符,

cmd:用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。 

返回值:
  ioctl函数的实现是根据命令执行的一个switch语句,但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数);

下表列出了网络相关ioctl 请求的request 参数以及arg 地址必须指向的数据类型:

类别

Request

说明

数据类型

SIOCATMARK

SIOCSPGRP

SIOCGPGRP

是否位于带外标记

设置套接口的进程ID 或进程组ID

获取套接口的进程ID 或进程组ID

int

int

int

 

FIONBIN

FIOASYNC

FIONREAD

FIOSETOWN

FIOGETOWN

设置/ 清除非阻塞I/O 标志

设置/ 清除信号驱动异步I/O 标志

获取接收缓存区中的字节数

设置文件的进程ID 或进程组ID

获取文件的进程ID 或进程组ID

int

int

int

int

int

 

 

 

 

 

 

 

 

 

SIOCGIFCONF

SIOCSIFADDR

SIOCGIFADDR

SIOCSIFFLAGS

SIOCGIFFLAGS

SIOCSIFDSTADDR

SIOCGIFDSTADDR

SIOCGIFBRDADDR

SIOCSIFBRDADDR

SIOCGIFNETMASK

SIOCSIFNETMASK

SIOCGIFMETRIC

SIOCSIFMETRIC

SIOCGIFMTU

SIOCxxx

获取所有接口的清单

设置接口地址

获取接口地址

设置接口标志

获取接口标志

设置点到点地址

获取点到点地址

获取广播地址

设置广播地址

获取子网掩码

设置子网掩码

获取接口的测度

设置接口的测度

获取接口MTU

(还有很多取决于系统的实现)

struct ifconf

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

struct ifreq

ARP

SIOCSARP

SIOCGARP

SIOCDARP

创建/ 修改ARP 表项

获取ARP 表项

删除ARP 表项

struct arpreq

struct arpreq

struct arpreq

SIOCADDRT

SIOCDELRT

增加路径

删除路径

struct rtentry

struct rtentry

I_xxx

  • 文件IO和标准IO的区别

文件IO和标准IO的区别(详解)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡沫o0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值