在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 命令。
库函数
针对输入输出操作直接使用底层系统调用效率非常低,原因由如下两点。
- 使用系统调用会影响系统性能。
- 硬件会对底层系统调用一次所读写的数据块大小做限制。磁盘:至少一个扇区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 进行翻阅。限于篇幅,后续文章会对文件操作的阻塞个非阻塞进行进一步的了解。