版权声明:本文为博主原创文章,转载请注明博客地址: https://cloud.tencent.com/developer/article/1454420
Linux文件描述符
在Linux下当一个进程打开文件的时候,OS会返回相应的文件描述符,程序为了处理该文件必须使用这个文件描述符。文件描述符是一个正整数。一般而言,当一个进程启动的时候,他会打开3个文件:标准输入,标准输出,标准错误。这3个文件对应的文件描述符分别是0,1,2.通常使用宏:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO.文件描述符是一个索引,指向内核中打开文件的记录表。
Linux操作系统给我们提供了6个系统调用create,open,write,close,read,lseek。系统调用是不带缓冲区的。他们是POSIX标准提供的。这些函数需要的头文件#include<fcntl.h>,#include<sys/types.h>,#include<sys/stat.h>。可以使用man命令来查看这些函数的用法以及所需要的头文件。首先,使用man man命令可以知道,系统调用在第二章。
使用命令man 2+函数名字就可以看到系统调用的详细描述。
open函数和creat函数
参数pathname是路径;flags非常多,可以从手册中看到,常用的就是 O_RDONLY, O_WRONLY, O_RDWR等。mode标示了对文件的访问权限,如下所述。
mode值包含了对文件的访问权限位。正如上面描述的一样,每个文件有9个访问权限位,并且可以分为3组。
mode | 含义 |
---|---|
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IROTH | 其他读 |
S_IWOTH | 其他写 |
S_IXOTH | 其他执行 |
如果打开的文件是在某个目录文件下,那么该目录必须是可执行的,因为对于目录文件而言,可执行代表着搜索位,我们可以找该目录下的文件。目录的读只代表我们可以读取该目录的文件列表,不能进行其他操作。如果当前打开了一个文件,如果是root用户的进程,那么它肯定能访问该文件。如果进程是文件所有者执行的,那么对文件的权限取决于第一组的权限;如果进程是文件所有者所在组或者附属组之一,那么对文件的权限取决于第二组权限。若进程是其他用户执行的,那么对文件的操作取决于第三组权限。
在使用open函数打开一个文件的时候,最常用的三个参数是:O_WRONLY(只写),O_RDONLY(只读),O_WRRD(可读可写)另外两种是:O_EXEC(执行),O_SEARCH(搜索,应用于目录)。另外open打开的文件,返回的文件描述符一定是最小的未使用描述符。path所指定的路径可以是绝对路径,也可以是相对路径。
flags中有些参数可以帮助我们创建文件
creat函数的不足之处是它创建的文件以只写的方式打开。当我们拥有上述参数的时候,就可以使用open函数来代替creat函数创建文件。即:
open(path,O_RDWR|O_CREAT|O_TRUNC,mode)
open函数成功时,返回一个文件描述符;若出错,返回 -1。
如果返回了-1,表示出错,我们还可以由perror函数知道出现的错误具体是什么。
当Linux系统函数出错的时候,一般会返回一个负值给errno。POSIX和ISO C将errno定义为一个符号。它可以是一个包含出错编号的整数,也可以是一个返回出错编号指针的函数,具体由开发者去实现。C定义了perror函数来打印出错信息。
perror函数首先输出参数s的内容,然后是一个冒号,一个空格,接着输出errno所对应的出错消息。然后换行。
read函数
fd是文件描述符;buf是缓冲区,用于保存从文件中读取的内容。count是读取的字节数。如果read成功,返回读取到的字节数。若已到达文件尾端,返回0。读取出错返回-1。
write函数
如果写入成功,返回以写字节数,否则,返回-1。参数的意义和read函数一致。
close函数
关闭一个文件并释放该进程加在该文件上的所有锁。当一个进程终止的时候,会自动关闭它打开的所有文件。所以有时候并不显式的使用close关闭文件。close函数返回0表示成功,返回-1表示错误。
lseek函数
每个打开文件都有一个与其相关联的“当前文件偏移量”。用于计算从文件开始处的字节数。通常,读写都是从当前文件偏移量处开始的,并使用偏移量增加所读写的字节数。系统默认该偏移量为0。可以使用lseek函数来指定一个打开文件的偏移量。
参数whence表示从哪儿开始。它有3个值,如下。
offset的值的意义具体就根据whence参数的值来决定。
一个简单的例子如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
int fd;
char str[20] = {"Hello World!"};
char buf[20] = {0}; //初始化为0,为了方便打印。
int flag;
//在当前路径下打开一个new.txt文件,若不存在就创建它;若存在则以可读可写方式打开,并且从长度0截断
fd = open("./new.txt",O_CREAT|O_TRUNC|O_RDWR,0777);
if (-1 == fd)
{
perror("open fail"); //包含在stdio中的函数。
exit(1);
}
flag = write(fd,str,20); //给文件写入内容
if(-1 == flag)
{
perror("write fail");
exit(1);
}
lseek(fd,0,SEEK_SET); //使用lseek将文件偏移量设置为文件开始处
//如果没有这一步,那么下面的read无法读取到文件内容,因为文件偏移量已经到了字符串结束符处。
flag = read(fd,buf,20); //读取文件内容
if(-1 == flag)
{
perror("read fail");
exit(1);
}
flag = close(fd); //关闭文件
if(-1 == flag)
{
perror("close fail");
exit(1);
}
printf("%s\n",buf); //打印缓冲区中的内容。
return 0;
}
打印结果如下:
需要注意的是,buf数组需要全部初始化为0,'\0'的ASCII就是0.这样将打开的文件中读取的文本信息打印的时候才能正常打印,不会乱码。否则不知道在哪儿终止,将会产生乱码。
注意:在使用Linux的系统调用操作文件的时候,是无缓冲的,这点很重要。当你在做少量,大批次写入的时候效率会很低。因此注意使用缓冲(用数组的之类的暂时保存一下),能提高I/O效率。
lseek不可以用于管道,FIFO,socket文件。另外lseek的文件偏移量的大小可以大于当前文件的长度,在这种情形下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞。文件空洞并不要求在磁盘上占据空间。