Linux系统编程之文件IO

在Linux系统中,一切皆是文件。文件相当于操作系统和设备之间的一架桥梁。程序可以像使用文件那样访问磁盘文件,串行口,打印机等其他设备。通常情况下,我们可以通过以下5个函数,即可操作众多设备:open、close、read、write和ioctl。例外的情况: 目录的读写,网络连接等特殊文件

文件IO介绍

文件和设备

/dev/console - 系统控制台。  
/dev/tty - 访问不同的物理设备。   
/dev/null - 空设备,向所有写这个设备的输出都将被丢弃。

设备驱动程序

操作系统的核心部分,即内核,是由一组设备驱动程序组成。他们是一组对系统硬件进行控制的底层接口,为了向用户提供一个一致的接口,其封装了所有与硬件相关的特性。
硬件特有功能可通过ioctl(用于I/O控制)系统调用来提供。

/dev 目录下的设备文件都可以被打开、读、写和关闭。
1)open : 打开文件或设备。
2)read : 从打开的文件或设备里读数据。
3)write: 向文件或设备写数据。
4)close: 关闭文件或设备。
5) ioctl:  把控制信息传递给设备驱动程序,每个驱动都由自己的一组 ioctl 命令。

库函数

针对输入输出操作直接使用底层系统调用效率非常低,原因由如下两点。

  1. 使用系统调用会影响系统性能。
  2. 硬件会对底层系统调用一次所读写的数据块大小做限制。磁盘:至少一个扇区512字节,磁带,一次 10K

库函数给设备和磁盘文件提供了更高层的接口,即标准函数库。使用它你可以高效读写任意长度的数据块,库函数则在数据满足条件后再安排系统调用。这样极大降低了开销。
注:库函数的文档一般放在手册的第三页,每个库函数有其对应的头文件。

底层文件访问

运行中的程序称为进程,每个进程都有与之关联的文件描述符。

文件描述符 - 一些小值整数,通过他们访问打开的文件或设备。一旦一个进程启动,内核就会分配三个文件描述符FILE:

0:   标准输入   STDIN_FILENO
1:   标准输出   STDOUT_FILENO
2:   标准错误   STDERR_FILENO

文件描述符的变化范围是:0~OPEN_MAX-1,默认为1024(可通过ulmit -a 查看)

查看支持打开文件个数 cat /proc/sys/fs/file_max

系统函数介绍

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);
描述

通过给定一个文件名或者路径,成功调用后,进程中返回系统能够分配最小的文件描述符。此文件描述符进程内唯一,两个不同进程访问同一个文件所分配的文件描述符不一定相同。

参数说明
pathname - 指示准备打开的文件或设备的名字;
flags    - 用于指定打开文件所采取的动作;
mode    - 用于指定创建文件的权限,指定动作为 O_CREATE 才使用。

必须制定的flag操作:

模式 说明
O_RDONLY 以只读方式打开
O_WRONLY 以只写方式打开
O_RDWR 以读写方式打开

可选的flag操作

  • O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾
    而不覆盖原来的内容。
  • O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该
    文件的访问权限。
  • O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
  • O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Trun-
    cate)为0字节。
  • O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/
    O),后续博客详细了解阻塞和非阻塞代码编程

权限位 mode

可以用八进制数表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。
注意文件权限由open的mode参数和当前进程的umask掩码共同决定。

// 查看shell当前umask值
allies@allies:~$ umask
0002

用touch命令创建一个文件时,创建权限是0666,而touch进程继承了Shell进程的umask
掩码,所以最终的文件权限是0666&∼0002=0664

read

概述
#include <unistd.h>

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

从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中

参数说明
fd: 文件唯一描述符
buf: 缓冲区数组起始地址
count: 读取文件大小

返回值: 成功返回读取到的字节大小,失败返回-1,并置位 errno。

EINTR  在读取到数据以前调用被信号所中断.
EAGAIN 使用 O_NONBLOCK 标志指定了非阻塞式输入输出,但当前没有数据可读.
EFAULT buf 超出用户可访问的地址空间.
具体查看 Man Page。

write

在一个文件描述符上执行写操作

概述
#include <unistd.h>

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

write 向文件描述符 fd 所引用的文件中写入从 buf 开始的缓冲区中 count 字节的数据。POSIX规定,当使用了write()之后再使用 read(),那么读取到的应该是更新后的数据。

返回值

write 向文件描述符 fd 所引用的文件中写入 从 buf 开始的缓冲区中 count 字节的数据。POSIX规定,当使用了write()之后再使用 read(),那么读取到的应该是更新后的数据.失败返回-1,并置位errno

EBADF  fd 不是一个合法的文件描述符或者没有以写方式打开
EINVAL fd 所指向的对象不可写
EFAULT buf 不在用户可访问地址空间内
EAGAIN 读操作阻塞,但使用 O_NONBLOCK 指定了非阻塞式输入输出
EINTR  在写数据以前调用被信号中断
具体查看 Man Page

close

关闭一个文件描述符

概述
#include <unistd.h>

int close(int fd);
描述

close 关闭一个文件描述符,使它不在指向任何文件和可以在新的文件操作中被再次使用。任何与此文件相关联的以及程序所拥有的锁,都会被删除 (忽略那些持有锁的文件描述符)
假如 fd 是最后一个文件描述符与此资源相关联,则这个资源将被释放。若此描述符是最后一个引用到此文件上的,则文件将使用 unlink(2) 删除。

参数说明

fd: 需要关闭文件的文件描述符

返回值

close 返回 0 表示成功,或者 -1 表示有错误发生,置位 errno

EBADF  fd 不是一个有效的已被打开的文件的描述符
EINTR  函数 close() 调用被一信号中断
EIO    I/O 有错误发生

ioctl

ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是
不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是
in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数
据。例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位通
过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过ioctl设置。

概述
#include <sys/ioctl.h>

int ioctl(int d, int request, ...);
描述

d是某个设备的文件描述符。request是ioctl的命令,可变参数取决于request,通常是
一个指向变量或结构体的指针。若出错则返回-1,若成功则返回其他值,返回值也是取决于
request。

返回值

调用成功,返回 0 。出错返回 -1 ,置位 errno

EBADF  fd 不是一个有效的文件描述符
EFAULT 参数引用内存溢出
EINVAL 无效request或参数
更多参考 Man Page

ioctl提供了一个用于控制设备及其描述行为和配置底层的服务的接口。终端文件描述符、套接字都可以定义他们的ioctl,具体需要参考特定设备的手册。

示例代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string.h>

int main(int argc, char* argv[])
{

    printf("argc = %d\n", argc);
    printf("argv = %s\n",argv[1]);

    if(argc<2)
    {
        printf("open file need  args!\n");
        exit(1);
    }

    //置位创建文件权限
    umask(0000);
    int fd = -1;
    // 打开文件,不存在创建,并且指定读写方式
    fd = open(argv[1],O_CREAT | O_RDWR, 0777);
    char buf[512] = "This is a test file!";
    // 将字符数组写入文件中
    int size = write(fd, buf,strlen(buf));
    printf("write size = %d\n",size);
    printf("fd = %d\n",fd);
    close(fd);

    // 再次打开用户输入的文件
    fd = open(argv[1],O_RDONLY);
    // 重新读取文件中的内容
    size = read(fd,buf,sizeof(buf));
    printf("size = %d\n",size);
    printf("read : %s\n",buf);
    close(fd);

    return 0;
}

总结

本篇文章为本人第一次接触linux系统编程所学习的,内容设计最直接的文件io操作。文章中的大部分原型代码都可以通过 Linux 自带的 Man Page 进行翻阅。限于篇幅,后续文章会对文件操作的阻塞个非阻塞进行进一步的了解。

参考博客

坚持原创技术分享,您的支持将鼓励我继续创作!