三、文件IO(1)

文件I/O(1)

引言

前面对UNIX的基础知识进行了简单介绍,接下来将介绍UNIX的细节部分。首先介绍文件I/O。

1.文件描述符

对于内核而言,所有打开的文件都通过文件描述符进行引用。文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。

UNIX系统将文件描述符0于标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准错误相关联,但同时在符合POSIX.1标准的应用程序中,0==STDIN_FILENO、1==STDOUT_FILENO、2==STDERR_FILEENO.这些常量都在中定义。

2.函数openopenat

调用函数openopenat可以打开或创建一个文件。

#include
int open(const char* path,int oflag,...); //...表示余下的参数数量及类型时可变的,oflag参数用来说明函数的多个选项。
int openat(int fd,const char* path,int oflag,...);

若打开成功则返回文件描述符,不成功则返回-1.

oflag常用参数:

  • O_WRONLY:以只写模式打开文件。

  • O_RDWR:以读写模式打开文件。

  • O_CREAT:如果文件不存在,则创建文件。

  • O_EXCL:与 O_CREAT 一起使用,如果文件已存在,则打开失败。

  • O_TRUNC:如果文件已存在,将其截断为零长度。

  • O_APPEND:在文件末尾追加内容,而不是覆盖原有内容。

  • O_SYNC:每次写入操作都会等待物理 I/O 完成,确保数据被写入磁盘。

  • O_NONBLOCK:以非阻塞方式打开文件。

  • O_RDONLY:以只读方式打开文件

    示例

    #include 
    #include"apue.h"
    int main(int argc,char *argv[]) {
        int fileDescriptor = open(argv[1], O_RDONLY);
        if (fileDescriptor != -1) {
            printf("打开成功\n");
            close(fileDescriptor); // 关闭文件
        } else {
            printf( "无法打开文件!\n");
        }
    
        exit(0);
    }
    

创建一个data文件进行验证,如图所示

三、文件IO(1)_第1张图片

另外oflag参数还可以通过”或“运算符进行连接:

int fileDescriptor = open("example.txt", O_CREAT | O_WRONLY | O_TRUNC); //我们使用 O_CREAT 参数创建了一个新文件,并使用 O_WRONLY 表示以只写模式打开文件。另外,我们还使用 O_TRUNC 标志将文件截断为零长度(如果文件已存在)。

openat函数相对于open函数参数多了个fd,具有额外的灵活性,可以基于给定的文件描述符或路径来打开文件。open 函数使用的是绝对路径,而 openat 函数可以使用相对路径,并以给定的文件描述符(通常是目录的文件描述符)作为参考点来解析路径。

openat函数的使用有三种可能性:

  1. path参数指定的是绝对路径名,这时fd参数被忽略,相当于open函数

  2. path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取。

  3. path参数指定相对路径名,fd参数具有特殊值AT_FDCWDopenat 函数将使用当前工作目录作为参考点,解析相对路径名,并打开相应的文件。

    #include 
    #include "apue.h"
    
    int main(int argc,char* argv[]) {
        // 示例 1:使用绝对路径名
        int fileDescriptor1 = openat(AT_FDCWD,argv[1], O_RDONLY);
        if (fileDescriptor1 != -1) {
            printf("文件成功打开\n");
            close(fileDescriptor1); // 关闭文件
        } else {
            printf("无法打开文件!\n");
        }
       exit(0);
    }
    
    

    将绝对路径作为参数传入:

    image-20230911214552311

       #include
       #include "apue.h"
       // 示例 2:使用相对路径名和 fd 参数
       int main(int argc,char* argv[]){
           int dirfd = open(argv[1], O_RDONLY);
           if (dirfd == -1) {
               err_sys("无法打开目录 %s",argv[1]);
        }
    
           int fileDescriptor2 = openat(dirfd, argv[2], O_RDONLY);
           if (fileDescriptor2 != -1) {
               printf("文件成功打开\n");
               close(fileDescriptor2); // 关闭文件
           } else {
               err_sys("无法打开文件%s",argv[2]);
        }
           close(dirfd); // 关闭目录
           exit(0);
       }
    
    

    将相对路径作为参数传入:

    三、文件IO(1)_第2张图片

    #include
    #include "apue.h"
    int main(int argc,char* argv[]){
        // 示例 3:使用相对路径名和 AT_FDCWD 参数
        int fileDescriptor3 = openat(AT_FDCWD, argv[1], O_RDONLY);
        if (fileDescriptor3 != -1) {
           printf("文件成功打开\n");
           close(fileDescriptor3); // 关闭文件
        } else {
           err_sys("无法打开文件%s",argv[1]);
        }
        exit(0);
    }
    

    将相对路径作为参数传入:

    image-20230911220243058

3.函数lseek

每个打开文件都有一个与其相关联的“当前文件偏移量”。它通常是非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。

#include 
#include 

off_t lseek(int fd, off_t offset, int whence);

  • fd:文件描述符,表示要操作的文件。
  • offset:相对于 whence 的偏移量,可以是正数、负数或零。
  • whence:指定偏移量的基准位置,可以是以下值之一:
    • SEEK_SET:从文件开头开始计算偏移量。
    • SEEK_CUR:从当前文件位置开始计算偏移量。
    • SEEK_END:从文件末尾开始计算偏移量。

返回值:

  • 若成功,返回新的文件偏移量(相对于文件开头)。
  • 若失败,返回 -1,并设置 errno 错误代码来指示错误类型。

检测标准输入能否设置偏移量:

#include "apue.h"

int main(){
    if (lseek(STDIN_FILENO,0,SEEK_CUR)==-1){
        printf("cannot seek\n");
    }else{
        printf("seek OK\n");
    }
    exit(0);
}

三、文件IO(1)_第3张图片

./lseek1 < /etc/passwd //直接将 /etc/passwd 文件的内容作为 lseek1 可执行文件的标准输入
cat < /etc/passwd| ./lseek1  //首先使用 cat 命令读取 /etc/passwd 文件的内容,并将其输出到标准输出。然后,| 管道符将标准输出连接到 ./lseek1 可执行文件的标准输入。

lseek仅将当前文件的偏移量记录在内核中,并不引起任何I/O操作,该偏移量用于下一个读或写操作。

实例

#include "apue.h"
#include 

char buf1[]="abcdefghij";
char buf2[]="ABCDEFGHIJ";

int main(){
    int fd;
    if((fd=creat("file.hole",FILE_MODE))<0){ //调用 creat 函数创建了一个名为 "file.hole" 的文件,并将返回的文件描述符赋值给变量 fd。如果创建失败,则会调用 err_sys 函数打印错误信息并退出程序。
        err_sys("creat error");
    }

    if(write(fd,buf1,10)!=10){ //调用 write 函数向文件写入 buf1 中的内容,写入长度为 10。如果写入失败,则会调用 err_sys 函数打印错误信息并退出程序。
        err_sys("buf1 write error");
    }

    if(lseek(fd,16834,SEEK_SET)==-1){ //调用 lseek 函数将文件指针移动到偏移量 16834 处。SEEK_SET 表示从文件起始处计算偏移量。如果移动失败,则会调用 err_sys 函数打印错误信息并退出程序。
        err_sys("lseek error");
    }

    if(write(fd,buf2,10)!=10){ //调用 write 函数向文件写入 buf2 中的内容,写入长度为 10。如果写入失败,则会调用 err_sys 函数打印错误信息并退出程序。
        err_sys("buf2 write error");
    }

    exit(0);
}
vi lseek2.c
gcc lseek2.c -o lseek2 -l apue
./lseek2
ls -l file.hole
od -c file.hole

三、文件IO(1)_第4张图片

文件偏移量可以大于文件长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,位于文件中但没有写过的字节都被读为0.比如上述示例,首先往file.hole里面读取10个字节的数据,然后设置文件偏移量为16834,再接着往文件中读取10个字节数据。

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
*
0040700  \0  \0   A   B   C   D   E   F   G   H   I   J
40700对应位置就是16832,在过两个位置到A的位置也即16834,由此可见设置偏移量后文件中间位置都为空洞,写入从偏移位置开始写。

6.函数read

函数原型:

#include 

ssize_t read(int fd, void *buf, size_t count);

参数说明:

  • fd:要读取的文件描述符。
  • buf:用于存储读取数据的缓冲区。
  • count:要读取的最大字节数。

read函数会尝试读取最多count个字节的数据,并将其存储到buf指向的缓冲区中。它返回实际读取的字节数,若出错则返回-1。

在使用read函数时,需要注意以下几点:

  1. fd参数必须是一个已经打开的文件描述符,例如通过open函数或者其他文件操作函数获取的。
  2. buf参数必须是一个有效的指针,指向足够容纳count个字节的内存空间。
  3. read函数是一个阻塞调用,当没有足够的数据可供读取时,它会一直等待,直到满足条件才返回。
  4. read函数可能会读取少于请求的字节数,这是正常现象,因此在使用时需要根据返回值进行处理。

7.函数write

write 函数是在 UNIX 操作系统中用于向文件描述符写入数据的函数。其函数原型如下:

#include 

ssize_t write(int fd, const void* buffer, size_t count);

参数说明:

  • fd:要写入的文件描述符。
  • buffer:要写入的数据的指针。
  • count:要写入的数据的字节数。

write 函数会尝试将 buffer 指向的数据写入到文件描述符 fd 所描述的文件中。它返回实际写入的字节数,若出错则返回-1。

在使用 write 函数时,需要注意以下几点:

  1. fd 参数必须是一个已经打开的文件描述符,例如通过 open 函数或其他文件操作函数获取的。
  2. buffer 参数必须是一个有效的指针,指向要写入的数据。
  3. count 参数表示要写入的字节数,如果写入成功,则返回写入的实际字节数,它可以少于请求的字节数,这是正常现象。
  4. write 函数通常是一个阻塞调用,它会一直等待直到写入的缓冲区内容被完全传输到内核缓冲区,然后才返回。
    数会尝试将 buffer 指向的数据写入到文件描述符 fd 所描述的文件中。它返回实际写入的字节数,若出错则返回-1。

在使用 write 函数时,需要注意以下几点:

  1. fd 参数必须是一个已经打开的文件描述符,例如通过 open 函数或其他文件操作函数获取的。
  2. buffer 参数必须是一个有效的指针,指向要写入的数据。
  3. count 参数表示要写入的字节数,如果写入成功,则返回写入的实际字节数,它可以少于请求的字节数,这是正常现象。
  4. write 函数通常是一个阻塞调用,它会一直等待直到写入的缓冲区内容被完全传输到内核缓冲区,然后才返回。

你可能感兴趣的:(UNIX,linux,开发语言,unix)