文件描述符
在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。
程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。
#define STDIN_FILENO 0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符
在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。
常用 I/0 函数
需要的头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int open(const char *pathname, int flags, mode_t mode);
功能:
打开文件,如果文件不存在则创建。
参数:
pathname: 文件的路径及文件名。
flags: 打开文件的行为标志,如,以只读方式(O_RDONLY,第一个为字母不是数据)打开,以读写或新建新文件的方式(O_RDWR|O_CREAT)打开。
mode: 这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限(文件权限详情,请点此链接)。
返回值:
成功:成功返回打开的文件描述符
失败:-1
int close(int fd);
功能:
关闭已打开的文件
参数:
fd: 文件描述符,open()的返回值
返回值:
成功:0
失败:-1
ssize_t write(int fd, const void *addr, size_t count);
功能:
把指定数目的数据写到文件(fd)
参数:
fd: 文件描述符
addr: 数据首地址
count: 写入数据的长度(字节),一般情况下,数据有多少,就往文件里写多少,不能多也不能少
返回值:
成功:实际写入数据的字节个数
失败:-1
ssize_t read(int fd, void *addr, size_t count);
功能:
把指定数目的数据读到内存(缓冲区)
参数:
fd: 文件描述符
addr: 内存首地址
count: 读取的字节个数
返回值:
成功:实际读取到的字节个数
失败:-1
实战示例
接下来,我们使用以上 4 个系统调用 I/O 函数写一个程序,能实现像系统命令 cp 的功能:
使用 open() 打开源文件,使用 read() 从文件读数据,使用 write() 向目的文件写数据,示例代码如下:
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- int main(int argc, char *argv[])
- {
- if((argc == 3) && (strcmp(argv[1], argv[2]) != 0))
- {// 保证有 3 个参数,而且源文件和目的文件名字不能一样
- int fd_src, fd_dest, ret;
- //只读方式打开源文件
- fd_src = open(argv[1], O_RDONLY);
- if(fd_src < 0)
- {
- perror("open argv[1]");
- return -1;
- }
- // 新建目的文件
- fd_dest = open(argv[2], O_WRONLY|O_CREAT, 0755);
- if(fd_dest < 0)
- {
- close(fd_src);
- perror("open argv[2]");
- return -1;
- }
- do
- {
- char buf[1024] = {0};
- // 从源文件读取数据
- ret = read(fd_src, buf, sizeof(buf));
- // 把数据写到目的文件,注意最后一个参数,有多少写多少
- write(fd_dest, buf, ret);
- }while(ret > 0);
- // 关闭已打开的文件
- close(fd_src);
- close(fd_dest);
- }
- return 0;
- }
运行结果如下:
文件IO
1. 引言
大多数LInux文件IO只需用到5个函数:open read write lseek close.
2. 文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,
用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
在POSIX.1应用程序中,幻数0、1、2应被代换成符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文件<unistd.h>中。
每个进程运行时,系统已分配3个文件描述符(0,1,2),分别对应标准输入, 标准输出, 标准错误输出
3. 文件IO中常用的函数
3.1 open:
打开一个文件或者设备文件, 返回一个文件描述符。当操作此文件描述符时,就是操作相应的文件或设备
int open(char *pathname,int falgs,.../*mode*/)
flags 必须指其中O_RDONLY O_WRONLY O_RDWR唯一项
可选项:
O_APPEND 每次写操写都将文件指针定位文件尾处
O_CREAT 如果文件不存在创建新文件
O_EXCL 如指定O_CREAT时文件存在 出错返回
O_TRUNC 必须以O_WRONL或O_RDWR进行操作,把文件清空
O_NONBLOCK 以非阻塞的方式打开
O_NOCTTY 如果打开文件为终端设备,不将该设备分配为此进程的控制终端
O_SYNC 每次write都等到I/O操作完成,并等文件的属性更新
O_RSYNC 作为read操作等侍,直到任何对同文件部分未决的写入完成
O_DSYNC 每次write都等到I/O操作完成,不等文件的属性更
mode指定创建文件的权限
//创建一个文件,假设这个文件存在时要清空
open("文件", O_RDWR|O_CREAT|O_TRUNC, 0777);
fd = open("txt", O_RDONLY | O_CREAT | O_EXCL, 0644);
3.2 creat
int creat(char *pathname,mode_t mode)
等价于:open(char *,O_WRONLY | O_CREAT | O_TRUNC, mode)
3.3 read
读取已经打开的文件中的数据。读了文件以后,文件描述符对应文件的offset会自动偏移。
ssize_t read(int fd, void *buf, size_t count);
从文件描述符fd指向文件里读取最大为count字节的数据放到buf指定的地址上去.
返回值: 为实际上读取的数值, 为0时读到文件尾, 为-1时错误
3.4 write
向指定的文件中写数据。成功写以后, 文件描述符的offset自动偏移
size_t write(int fd, const void *buf, size_t count);
把在buf地址指定的数据写到fd指向的文件里,最大写count字节.
成功返回写了多少字节, -1失败。write出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。
3.5 lseek
每个打开文件都有一个与其相关联的“当前文件位移量”。它是一个非负整数,用以度量
从文件开始处计算的字节数。就是改变文件描述符的偏移位置。
off_t lseek(int fildes, off_t offset, int whence);
whence:
SEEK_SET : 把偏移量设为offset, 从文件头开始.
SEEK_CUR : 把当前偏移量加上offset的值
SEEK_END : 先从文件尾开始偏移offset的值
返回值:成功返回定位之后的文件指针偏移 失败返回 -1
返回当前文件的偏移量
off_t currpos = lseek(fd, 0, SEEK_CUR);
3.7 close
把没用的文件描述符关掉,把此文件描述符重新分配.
int close(int fd);
3.8 dup
可用来复制一个现存的文件描述符.
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup2(oldfd, newfd) //让newfd成为oldfd的一个副本
dup2(fd, 1); //让fd替代标准输出
int ret=dup2(old,new)
如果new 打开的,则关闭new 返回新的文件描述符 失败返回-1. du2是一个原子操作。
dup2可以用newfd参数指定新描述符的数值。如果newfd当前已经打开,则先将其关闭再做dup2操作,如果oldfd等于newfd,则dup2直接返回newfd而不用先关闭newfd再复制。
3.9 fcntl
用于改变已打开的文件的性质 只能改变O_APPEND, O_NONBLOCK,O_ASYNC, O_DIRECT
int fcntl(int fileds,int cmd,.../*int arg */)
第三个参数除了cmd用于记录锁时为一个结构指针之外,其余均为整数
fcntl五种功能如下:
.复制一个现有的描述符 cmd=F_DUPFD
复制的新文件描述符清掉文件描述符标志 并且共享同一个文件表项
.获得/设置文件描述符标记 cmd=F_GETFD/F_SETFD
flags = fcntl(fd, F_GETFL); //获取
fcntl(fd, F_SETFL, flags); //设置
.获得/设置文件状态标志 cmd=F_GETFL/F_SETFL
忽略 O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC标志
由于历史原因O_RDONLY O_WRONLY O_RDWR并不各占一位,
它们之间互斥。因此首先必须用屏蔽字O_ACCMODE
取得访问模式然后与这三种flags比较
value=fcntl(fd,F_GETFL,0);
switch(value & O_ACCMODE){
case O_RDONLY :
}
.获得/设置导步I/O所有权cmd=F_GETOWN/F_SETOWN
.获得/设置记录锁 cmd=F_GETLK F_SETLK/F_SETLKW
3.10 pread pwrite
定位文件进行读写 不影响文件指针 偏移和读写操作为原子操作
3.11 sync
void sync(void)
:函数只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等实际写磁盘完成
int fsync(int fd)
:等待实际磁盘写操作完成,并且同步更新文件的属性,可用于数据库类型的应用程序
int fdatasync(int fd)
:类似于fsync,但只影响文件的数据部分不影响文件的属性
3.12 ioctl
int ioctl(int fd, int request, ...);
称之为I/O操作的垃圾箱 只要其字操函数不能或难于实现在 它都可能很容易做到
==========================================================
4. 某些系统下提供名为/dev/fd/N 等文件。打开文件/dev/fd/N 等效于复制N文件描述符(假定N描述符是打开的)
与其N共享文件表项
也有某些系统为/dev/fd/stdin /dev/fd/stdout 等,均为同等操作
homework:
1. 实现mycopy拷贝一个文件到另外一个文件(功能相当于 cp a.txt b.txt)
2. 实现mytouch 创建一个文件(功能相当于touch a.txt)。
3. 编写一个同dup2功能相同的函数,要求不调用fcntl函数并且要有正确的出错处理。
4. 在如启用添加标志打开一文件以便读、写,能否用lseek在任一位置开始读?能否用lseek更新文件中任一部分的数据?请写一段程序以验证之。