【Unix】分章复习笔记之第三章 · 文件IO (1)

it2023-11-23  81

【Unix】分章复习笔记之第三章 · 文件IO (1)

3.1 文件描述符

3.1.1 概念

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,用来指向被打开的文件。所有执行I/O操作的系统调用都会通过文件描述符。

值得注意的是:当读写一个文件的时候,使用open或create函数返回的文件描述符指向该文件,将其作为参数传递给read,write。

其中,0,1,2被标准化并在POSIX中将其替换为符号常量(在头文件<unistd.h>中):

STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO012

3.1.2 文件描述符与文件,进程的关系

每个文件描述符会与一个打开的文件相对应不同的文件描述符也可能指向同一个文件相同的文件可以被不同的进程打开,也可以在同一个进程被多次打开

在UNIX中,上图中的i-node表实际上是V表,下面会重点讲

3.2 函数open,openat

#include <fcnt1.h> int open(const char *path, int oflag,......) int openat(int fd, const char *path,int oflag,......) 成功返回文件描述符,失败返回-1

3.2.1 函数解释与区分

open和openat两个函数都可以==打开或创建一个文件。==不同点在于fd参数:

当path参数指定的是绝对路径名,这时,fd参数被忽略,openat与open无区分当path参数指定的是相对路径名,则fd参数指出相对路径名在文件系统中的开始地址。fd参数通过打开相对路径名所在的目录获取当path参数指定的是相对路径名,且fd参数位特殊值AT_FDCWD,此时,路径名在当前工作目录中获取。

注意:第二条的fd获取方式如下:

目录文件描述符的取得通常分为两步,先用opendir()函数获得对应的DIR结构的目录指针,再使用int dirfd(DIR*)函数将其转换成目录描述符

测试代码:

#include <apue.h> #include <fcntl.h> #include <dirent.h> int main() { int fd1,fd2,fd3,fd4; fd1 = open("/home/ubuntu/a.txt",O_RDWR | O_CREAT);//open常规用法 fd2 = openat(fd1,"/home/ubuntu/a.txt",O_RDWR | O_CREAT);//openat+绝对路径 DIR* dir = opendir("/home/ubuntu"); fd3 = dirfd(dir); fd4 = openat(fd3,"a.txt",O_RDWR | O_CREAT);//openat+相对路径 printf("fd1 = %d , fd2 = %d , fd4 = %d \n",fd1,fd2,fd4); return 0; }

结果:

可见,均打开成功。

3.2.2 oflag参数说明

oflag参数可以用来说明函数的多个选项。用下列的常量多个进行或运算构成完整的oflag参数。

参数名称作用O_RDONLY只读打开O_WRONLY只写打开O_EXEC只执行打开O_EDWR读写打开O_SEARCH只搜索打开(仅限目录)O_APPEND每次写时都追加到文件末尾*O_CLOEXEC把FD_CLOEXEC设置为文件描述符O_CREAT若此文件不存在则创建它O_DIRECTORY如果path引用的不是目录则出错O_EXCL测试一个文件是否存在*O_NOCTTY如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端*O_NOFOLLOW若path是符号链接,出错*O_NONBLOCK******************************************************************************************************************火星文,看不懂O_SYNC每次的write等待物理IO操作完成O_TRUNC若此文件存在,且为只写或读写成功打开,则长度截断为0*O_TTY_INIT******************************************************************************************************************火星文,看不懂*O_DSYNC每次的write等待物理IO操作完成,但是如果该写操作并不影响读取刚写的数据,不需等待文件属性更新。。。????????????????????*O_RSYNC使每一个以文件描述符作为参数的进行的read操作等待至所有对文件的统一部份挂起的写操作都完成

打*的非重点。这么多,谁记得住~~手码累死······

3.3 creat函数

#include <fcnt1.h> int creat(const char *path,mode_t mode); 若成功,返回为**只写打开**的文件描述符;否则,返回-1

此函数等效于:

open(path,O_WRONLY | O_CREAT | O_TRUNC,mode);

缺点:返回的文件描述符是只写打开,要读该文件必须得先close再open,很麻烦

现在我们都用:

open(path,O_RDWR | O_CREAT | O_TRUNC, mode);

3.4 close函数

#include <unistd.h> int close(int fd); 成功,返回0;否则返回-1

==关闭一个文件的同时会释放该进程的记录锁。==当一个进程终止时,内核会自动关闭它打开的所有文件,因此很多程序在open一个文件后没有close也是允许的。

3.5 lseek函数

3.5.1 off_t是个啥鬼?

off_t类型用于指示文件的偏移量,常就是long类型,其默认为一个32位的整数,在gcc编译中会被编译为long int类型,在64位的Linux系统中则会被编译为long long int,这是一个64位的整数,其定义在unistd.h头文件中可以查看。

3.5.2 函数原型

#include <unistd.h> off_t lseek(int fd, off_t offset, int whence); 成功返回文件的偏移量;失败返回-1 文件偏移量:每个打开的文件都有一个与其相关联的“当前文件偏移量”,它是一个非负整数。当前文件偏移量用来度量从文件开始到此刻位置的字节数。通常,读写操作都是从当前文件偏移量处开始。除非指定O_APPEND选项,其它情况下,打开一个文件时,文件偏移量为0。参数解释:fd:打开文件的文件描述符;offset:需要设置的文件偏移量;whence:符号常量,用于解释offset的作用。

3.5.3 常见的三个符号常量和示例代码

符号常量,代表的是函数原型中的whence,一般情况下常用的有三种:

SEEK_SETSEEK_CURSEEK_END将该处的文件偏移量设置为距离文件开始处offset字节将该文件的文件偏移量设置为当前值+offset,注意此处的offset可正可负将该处的文件偏移量设置为文件的长度+offset,offset可正可负

由于文件偏移量通常为非负整数,但是某些设备可能也允许负的文件偏移量。但是对于普通文件,其文件偏移量必须为非负。

示例代码:

#include "apue.h" #include <malloc.h> #include <unistd.h> #include <fcntl.h> int main() { int fd; char *str1,*str2,*str3; str1 = (char*)malloc(sizeof(char)*30); str2 = (char*)malloc(sizeof(char)*30); str3 = (char*)malloc(sizeof(char)*30); fd = open("/home/ubuntu/workspqce/TestText.txt",O_RDWR); off_t sk; sk = lseek(fd,0,SEEK_SET); ssize_t rd1,rd2,rd3; rd1 = read(fd,str1,30); sk = lseek(fd,30,SEEK_SET); sk = lseek(fd,-30,SEEK_CUR); rd2 = read(fd,str2,30); sk = lseek(fd,-30,SEEK_END); rd3 = read(fd,str3,30); printf("str1 = %s\n",str1); printf("str2 = %s\n",str2); printf("str3 = %s\n",str3); return 0; }

运行效果:测试文件TestText.txt见附件Codes and workspace

需要注意的是:==文件偏移量可以大于文件的长度。这种情况下,下一次的写操作会加长该文件,造成文件中的一个空洞。位于文件中且没有被写过的文件都读为0。==有些文件系统可以对空洞部分不实际分配存储空间。

3.6 read函数

#include <unistd.h> ssize_t read(int fd, void *buf, size_t nbytes); 返回读到的字节数,若到文件尾,反0;出错:返回-1

3.6.1 ssize_t是个啥鬼

ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,有没有注意到,它和long数据类型有啥区别?其实就是一样的。size_t 就是无符号型的ssize_t,也就是unsigned long/ unsigned int (在32位下),不同的编译器或系统可能会有区别,主要是因为在32位机器上int和long是一样的。在64位没有测试,但是参见百度百科,size_t 在64位下是64位,那么size_t的正确定义应该是typedef unsigned long size_t。

3.6.2 read函数需要注意的问题

当读普通文件时,文件长度 < nbytes,证明早早到达了尾端,此时返回文件长度数值且read到的只有文件长度的数据。如果作死再read,对不起,你啥都得不到,除了返回的0。从终端读入时,一次只能读取一行,当然,也有骚操作可以多读,这里就当不会从网络读时,由于网络的缓冲机制,可能造成返回值小于应有的字节数从管道或FIFO读时,如果管道包含的字节本身少于所需的数,就返回实际可用的字节数从面向记录的设备比如磁带读时,一次最多返回一个记录

还需要示例吗?前面一个已经使用过了

3.7 write函数

#include <unistd.h> ssize_t write(int fd, const void *buf, size_t nbytes); 若成功,返回已经写的字节数;否则返回-1

需要注意的点不多:

返回值通常和nbytes一致如果不一致,要么出错,要么磁盘写满,要么超过了一个给定进程的文件长度限制除非打开该文件时使用了O_APPEND参数,否则write的地方是当前文件偏移量

注意!!!不管是read还是write,运行后的当前文件偏移量是原文件偏移量+nbytes

示例代码:

#include "apue.h" #include <unistd.h> #include <malloc.h> #include <fcntl.h> int main() { int fd = open("/home/ubuntu/workspqce/TestText.txt",O_RDWR); char *buf = "\n----ADELE----\n"; int sk = lseek(fd,0,SEEK_SET); int wr1 = write(fd,buf,16); sk = lseek(fd,0,SEEK_END); int wr2 = write(fd,buf,16); printf("wr1 = %d , wr2 = %d\n",wr1,wr2); return 0; }

最新回复(0)