文件I/O的主要有五个常用的函数:
本章主要介绍不带缓存的I/O,所谓不带缓存I/O是指每一个调用都是内核的一个系统调用
对于内核而言,所有打开的文件都是通过文件描述符引用的。文件描述符0-标准输入,1-标准输出,2-标准错误。在POSIX规范中,已经提供了STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO来替代0、1、2数字,这样更加便于开发者理解。这些常量定义在<unistd.h>
头文件中
打开文件的函数如下:
#include <fctnl.h>
int open(const char* path, int oflag, ... /*mode_t mode*/)
int openat(int fd, const char* paht, int oflag, ... /*mode_t mode*/)
#define O_RDONLY 0x0000 /* open for reading only */
#define O_WRONLY 0x0001 /* open for writing only */
#define O_RDWR 0x0002 /* open for reading and writing */
#define O_ACCMODE 0x0003 /* mask for above modes */
创建一个文件,一般不使用,用open代替
#include<fcntl.h>
int creat(const char* path, mode_t mode);
相当于
open(paht, O_WRONY | O_CREATE | O_TRUNC, mode)
关闭一个打开的文件
#include<unistd.h>
int close(int fd);
当前文件偏移量,文件对写操作开始的位置。
正常非O_APPEND方式打开,偏移量会被重置为0。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
lseek可以形成文件空洞,实际上这个和内核实现无关,而是和文件系统相关,也就是说,允许空洞存在,如何存储空洞,都是归给文件系统的。目前大部分文件系统都是使用null填充。
#include "apue.h"
#include <fcntl.h>
char buf1[] = "abcdefghijk";
char buf2[] = "ABCDEFGHIJK";
int main()
{
int fd;
if ((fd=creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");
if (write(fd, buf1, 10) != 10)
err_sys("buf1 write error");
//offset now = 10
if (lseek(fd, 16384, SEEK_SET) == -1)
err_sys("lseek error");
//offset now = 16384
if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
//offset now=16394
exit(0);
}
使用od命令查看
yanke@yanke-pc:~/yanke/Code/apue/ch3$ od -c file.hole
0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000 A B C D E F G H I J
0040012
例子:判断一个标准输入能不能设置偏移量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
if (lseek(STDIN_FILENO,0,SEEK_CUR) == 1)
printf("cannot seek\n");
else
printf("seek OK\n");
exit(0);
}
//$./a.out < /etc/passwd
//seek OK
从打开的文件读取数据
#include<unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes)
//返回值:读取到的字符数目,如果到达文件尾,返回0,如果出错,返回-1
从打开的文件写入数据
#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
//返回值:成功返回写入的字节数,出错返回-1
将标准输入复制到标准输出
#include "apue.h"
#include <unistd.h>
#define BUFFSIZE 4096
int main()
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
{
if (write(STDOUT_FILENO, buf, n)!=n)
err_sys("write error");
}
if (n < 0)
{
err_sys("read error");
}
exit(0);
}
内核用于所用I/O的数据结构:内核使用3个数据结构表示打开的文件
1. 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表。包含文件描述符标志和指向文件表项的指针
2. 内核为所有打开文件维护一张文件表,每个文件表项包括:文件状态标志(读,写,添加,同步,阻塞等),当前文件偏移量,指向该文件的v节点。
3. 每个打开的文件有一个v节点,v节点包含文件类型和对此文件进行各种操作函数指针。对于大多数文件系统,v节点还包含i节点,这些i节点在文件打开时读入内存,包含文件的所有者,文件长度,指向文件实际数据快在磁盘所在位置的指针等。
如果两个独立的进程各自打开了同一个文件,则每个进程表指向自己的文件表项,文件表象指向相同的v节点。
Unix提供一种原子操作,每次写操作前都要将进程的当前偏移量设置到该文件的尾端,于是每次写之前就不需要调用lseek了。
#include <unistd.h>
ssize_t pread(int fd, void* buf, size_t nbytes, off_t offset);
size_t pwrite(int fd, const void* buf, szie_t nbytes, off_t offset);
用于复制一个文件:
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
dup执行后将会返回最小的可返回的文件描述符,dup2则是自定义文件描述符值,如果fildes2正在使用则关闭后再分配;如果fildes等于fildes2则只返回fildes2,且不关闭。
void sync(void);
int fsync(int fd);
int fdatasync(int fd);
int fcntl(int fildes, int cmd, ...);
cmd参数
例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
int val;
if (argc != 2)
{
printf("usage: a.out < descriptor# > \n");
exit(1);
}
if ((val=fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
{
printf("fcntl error for fd %d\n", atoi(argv[1]));
}
switch (val & O_ACCMODE)
{
case O_RDONLY:
printf("read only");
break;
case O_WRONLY:
printf("write only");
break;
case O_RDWR:
printf("read write");
break;
default:
{
printf("unkonwn access mode");
exit(1);
}
}
if (val & O_APPEND)
printf(", append");
if (val & O_NONBLOCK)
printf(", nonblocking");
if (val & O_SYNC)
printf(", synchronous writes");
#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
if (val & O_FSYNC)
printf(", synchronous writes");
#endif
putchar('\n');
exit(0);
}