1. 文件I/O
文件描述符:对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个文件或创建一个新文件时,内核向进程返回一个文件描述符。在UNIX系统中使用文件描述符0域进程的标准输入相关联,1与标准输出相关联,2与标准出错相关联,幻数0,1,2可分别用符号常量STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO代替,这些常量都定义在头文件<unistd.h>中。
open函数:调用open函数可以创建或打开一个文件。
#include<fcntl.h>
int open(constchar* pathname, int oflag);
int open(constchar* pathname, int oflag, mode_t mode);(成功返回文件描述符,出错返回-1)
pathname是要打开的或创建的文件的名字,oflag参数可以用来说明此函数的多个选项,用下列一个或者多个常量进行“或”运算构成oflag参数(这些常量包含在<fcntl.h>中)
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
以上三个常量必须指定一个且只能指定一个,一下是可选常量:
O_APPEND 每次写时都追加到文件的尾端
O_CREAT 若文件不存在,则创建它,使用此选项需要指定第三个参数mode,来设定文件的访问权限位
S_ISUID | 执行时的set-user-ID |
S_ISGID | 执行时的set-group-ID |
S_ISVTX | saved-text(粘滞位) |
S_IRWXU | 用户读、写、执行 |
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRWXG | 组读、写、执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IRWXO | 其他人读、写、执行 |
S_IROTH | 其他人读 |
S_IWOTH | 其他人写 |
S_IXOTH | 其他人执行 |
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则会出错,用此可以测试一个文件是否存在,如果不存在则创建此文件,这使测试和创建两个动作成为一个原子操作。
O_TRUNC 如果此文件存在,而且为只写或读打开,则将其文件长度截短为0
O_NOCTTY 如果pathname指的是终端设备,则不将该设备分配作为此经常的控制终端
O_NOBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞模式
creat函数:创建一个新文件
int creat(constchar* pathname, mode_t) (成功返回文件描述符,出错返回-1)
此函数等效于:open(pathname,O_WRONYL|O_CREAT|O_TRUNC, mode);
确定:creat的一个不足之处是它以只写的方式打开锁创建的文件,如果创建一个零时文件并要先写该文件然后读该文件则必须先调用creat, close 然后调用open,现在可以这样:
open(pathname,O_RDWR | O_CREAT |O_TRUNC, mode);
close函数:关闭一个打开的文件:
#include <unistd.h>
Int close(itn fileds);(成功返回0,出错返回-1)
当一个进程终止时,内核自动关闭它所有打开的文件,很多程序都利用了这个功能而不显式的close关闭打开的文件。
lseek函数:显式的为一个打开的文件设置偏移量
#include <unistd.h>
off_t lseek(int filedse, off_t offset, int whence);(成功返回新的文件偏移量,出错返回-1)
read函数:从打开的文件中读数据
#include<unistd.h>
ssize_t read(int filedse, void *buf, size_t nbytes);(成功返回读取到的字节数,若文件结尾返回0,出错返回-1)
write 函数:向打开的文件写数据
#include<unistd.h>
ssize_t write(int filedes, const void* buf, size_t nbytes);(成功返回已写的字节数,若出错返回-1)
实例:文件的拷贝
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
int main(int argc, char **argv)
{
int from_fd, to_fd;
int bytes_read, bytes_write;
char buffer[BUFFER_SIZE];
char *ptr;
if(argc != 3)
{
fprintf(stderr, "Usage:%s fromfile tofile\n\a", argv[0]);
exit(1);
}
if((from_fd = open(argv[1], O_RDONLY)) == -1)
{
fprintf(stderr, "Open %s Error:%s\n", argv[1],strerror(errno));
exit(1);
}
if((to_fd = open(argv[2], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) == -1)
{
fprintf(stderr, "Open %s Error: %s\n", argv[2], strerror(errno));
exit(1);
}
while(bytes_read = read(from_fd, buffer, BUFFER_SIZE))
{
if((bytes_read == -1) && (errno != EINTR))
break;
else if( bytes_read > 0)
{
ptr = buffer;
while(bytes_write = write(to_fd, ptr, bytes_read))
{
if((bytes_write == -1)&&(errno != EINTR))
break;
else if(bytes_write == bytes_read)
break;
else if(bytes_write > 0)
{
ptr += bytes_write;
bytes_read -= bytes_write;
}
}
}
}
close(from_fd);
close(to_fd);
exit(0);
}
dup,dup2 函数:赋值一个现存的文件描述符
#include <unistd.h>
int dup(int filedse);
int dup(int filedse, int filedes2);(成功返回新的文件描述符,失败返回-1)
这些函数返回的新文件描述符是当前可用文件描述符最小数值,dup2可以用filedse2指定新的文件描述符,返回的新的文件描述符域参数filedse共享一个文件表项,以及同一文件状态标志(读写添写等)以及同一文件偏移量;
复制一个文件描述符的另一个方法是使用fcntl函数:
fcntl(filedse, F_DUPFD, 0);
dup(filedse)等效于fcntl(filedse, F_DUPFD, 0),而调用dup2(filedse,filedse2)等效于close(filedse2); fcntl(filedse, F_DUPFD, filedse2);所以dup2是一个原子操作;
延迟写:大多数的I/O都是通过缓冲进行,当将数据写入文件时,内核通常先将该数据复制到其中的一个缓冲区,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重写该缓冲区以便存放其他磁盘块数据是,再将该缓冲排入输出队列,然后待其到达对首时,才进行实际的I/O操作,这种方式称作延迟写;
延迟写减少了磁盘读写的次数,但是却降低了文件内容的更新速度,使得欲写到文件的数据在一段时间内并没有写到磁盘上,当系统发生故障时,这种延迟可能造成文件的更新内容的丢失。
为了保证磁盘上实际的文件系统与缓冲区高速缓存中的内容的一致性,UNIX系统提供了sync, fsync, fdatasync三个函数:
#include<unistd.h>
int fsync(intfiledse);
int fdatasync(intfiledse);(成功返回0,出错返回-1)
void sync(void);是讲所有修改过得块缓冲区排到输出队列,然后就返回,不等待数据实际写到磁盘;
fcntl函数:改变已打开文件性质
#includ <fcntl.h>
Int fcntl(int filedse,int cmd, …./*int arg */);(成功则依赖于cmd返回,出错返回-1)