一、文件描述符
1、对于内核而言,所有打开的文件都通过文件描述符来表示。文件描述符是一个非负整数,当打开或者创建一个文件时,内核会向进程返回一个文件描述符。当对文件进行操作时,会使用到文件描述符。UNIX系统shell把0与标准输入,1与标准输出,2与标准错误相关联。所以在unix中文件描述符0,1,2被标准化,为提高可读性,定义了三个宏STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。在头文件<unistd.h>中定义。在早期文件描述符的最大范围定义为0~19,允许每个进程同时打开20个文件,现在很多系统将其上限增加至0~63.
二、open和openat
#include<fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int openat(int fd, const char *pathname, int flags);
int openat(int fd, const char *pathname, int flags, mode_t mode);
函数的返回值:若成功,返回文件描述符; 若出错,返回-1
openat打开的的是,相对于参数 fd 的相对地址pathname,以下是从网上借鉴了一个小例子,帮助理解:
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
void creat_at(char *dir_path, char *relative_path)
{
int dir_fd;
int fd;
int flags;
mode_t mode;
dir_fd = open(dir_path, O_RDONLY); //fd参数是通过打开相对路径名所在的目录来获取。
if (dir_fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
flags = O_CREAT | O_TRUNC | O_RDWR;
mode = 0640; //-rw-r-----
fd = openat(dir_fd, relative_path, flags, mode);
if (fd < 0)
{
perror("openat");
exit(EXIT_FAILURE);
}
write(fd, "HELLO", 5);
close(fd);
close(dir_fd);
}
int main()
{
creat_at("../openat", "log.txt");
return 0;
}
运行结果:
@debian69:~/algoAndSturct/UnixProgramTest/openat$ ./a.out
@debian69:~/algoAndSturct/UnixProgramTest/openat$ ls
a.out log.txt openatTest.c
liubowen@debian69:~/algoAndSturct/UnixProgramTest/openat$ ls -l
总用量 16
-rwxrwx--x+ 1 liubowen liubowen 5636 4月 24 11:35 a.out
-rw-r-----+ 1 liubowen liubowen 5 4月 24 11:35 log.txt
-rw-rwx-w-+ 1 liubowen liubowen 844 4月 24 11:34 openatTest.c
@debian69:~/algoAndSturct/UnixProgramTest/openat$ cat log.txt
HELLO@debian69:~/algoAndSturct/UnixProgramTest/openat$
通过上述结果,可以看到创建文件信息等 。
openat平时用的不多,主要的用途运用unix高级编程中的一段原文解释:
open与openat的权限以及flag参数解释,不详细整理。可自行百度
三、creat函数
该函数能被open完全替代,故只需要了解下,曾经被用过
#include<fcntl.h>
int creat(const char *pathname, mode_t mode);
函数的返回值:若成功,返回文件描述符; 若出错,返回-1
其等效与:
open(path, O_WRONLY | O_CREAT | O_TRUNC)
四、close函数
#include<unistd.h>
int close(int fd);
函数的返回值:若成功,返回0; 若出错,返回-1
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终结时,内核会自动关闭所有打开的文件,很多程序利用这一点,而不显式关闭文件。 好的习惯有开就有关,特殊情况可以考虑让内核去关。
五、lseek函数
1、文件偏移量:通常是一个非负整数,用以度量从文件开始处计算的字节数。通常来讲,文件的偏移量都是非负整数,但某些设备会允许返回负数,但对于普通文件,文件偏移量必须是非负数。故在判断返回值时,不要用 <0,而是用是否 == -1.
2、如果文件描述符指向一个管道、FIFO或者网络套接字,则lseek返回-1,并将errno设置为ESPIPE
3、lseek仅将当前的文件偏移量记录在内核中,他并没有引起任何的I/O操作。该偏移量只用于下一次的I/O操作。文件偏移量可以大于文件的当前长度,在这种情况下,对该文件下一次写将加长该文件,并造成文件中的空洞。这些空洞都被读为0。文件中的空洞在磁盘上不占用存储区。当定位到超出文件尾端之后开始写时,对于新写的数据需要分配磁盘块,但是对于源文件尾端和新开始写的数据之间的空洞部分,不需要分配磁盘块。
#include<unistd.h>
int lseek(int fd, off_t offset, int whence);
函数的返回值:若成功,返回新文件偏移量; 若出错,返回-1
六、read函数
#include<unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);
函数的返回值:读到的字节数,若读到文件尾部则返回 0; 若出错,返回-1
以下有几种情况实际读到的字节数少于要求读到的字节数(注意):
七、write函数
#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
函数的返回值:以写的字节数; 若出错,返回-1
八、I/O的读写效率
书中用了一个读写文件的小例子,对比了调整buffer大小之后,复制同样大小文件的时间。书中代码如下:
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
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);
}
可以使用:
time ./mycat < tmp.txt > file1.txt
进行测试时间(期间不多修改 BUFFSIZE 的大小)
这里要补充一点:
我们如果想测试复制1G大小的文件,又找不到正好1G大小的文件,可以用一下命令去创建一个1G大小的文件。
dd if=/dev/zero of=tmp.txt bs=1G count=1
具体的命令解释如下:
if=FILE : 指定输入文件,若不指定则从标注输入读取。这里指定为/dev/zero是Linux的一个伪文件,它可以产生连续不断的null流(二进制的0)
of=FILE : 指定输出文件,若不指定则输出到标准输出
bs=BYTES : 每次读写的字节数,可以使用单位K、M、G等等。另外输入输出可以分别用ibs、obs指定,若使用bs,则表示是ibs和obs都是用该参数
count=BLOCKS : 读取的block数,block的大小由ibs指定(只针对输入参数)
相信说明,可见该链接
书中用了20种不同的缓冲区做了尝试,其结果如下图:
下一篇