組み込み Linux 3 ファイル IO の入門

ファイル記述子

開いている Linux ファイルにはそれぞれ対応するファイル記述子があり、ファイル記述子は非負の整数です。open() 関数を呼び出すことでファイル記述子を取得できます。

シェルがプロセスを開始すると、プロセスはデフォルトで、次のように標準ファイル記述子と呼ばれる 3 つのファイル記述子を継承します。

ファイル記述子 使用 POSIX名 標準出力ストリーム
0 標準入力 STDIN_FILENO 標準入力
1 標準出力 STDOUT_FILENO 標準出力
2 標準誤差 STDERR_FILENO 標準エラー

プログラム内でこれらのファイル記述子を参照する場合、数値を直接使用することも、<unistd.h> **(推奨)** で定義された POSIX 名を使用することもできます。ただし、stdio ストリームに対応するファイル記述子は静的ではなく、単なる初期値です。freopen() 関数を呼び出すと、stdio ストリームに対応するファイル記述子の値が変更される可能性があります。次のリファレンスを参照してください。

変数 stdin、stdout、および stderr は、最初はプロセスの標準入力、出力、およびエラーを参照しますが、 freopen() ライブラリ関数を使用すると、任意のファイルを参照するように thry を変更できますfreopen() は、操作の一部として、再度開かれたストリームの基礎となるファイル記述子を変更する場合があります。つまり、たとえば、 stdout でfreopen() を実行した後、基になるファイル記述子がまだ 1 であると想定するのは安全ではなくなります。

ファイル入出力機能

ファイル I/O を実行するための 4 つの主要なシステム コール関数は次のとおりです (言語とソフトウェア パッケージは通常、C ライブラリのファイル操作関数 fopen()、fread など、二次的にカプセル化された I/O 関数を使用して間接的に呼び出します) ( )、fwrite()、fclose()):

int open(const char *pathname, int flags, mode_t mode);
// 打开pathname所指定的文件,并返回文件描述符,后续的函数使用文件描述符指代打开的文件,可以通过设置掩
// 码参数flags的方式在文件不存在时创建之, mode参数用于指定创建文件时文件的访问权限。
ssize_t read(int fd void *buf, size_t count);
// 从fd文件描述符所指代的文件中读取最多count字节的数据到缓冲buf,函数返回实际读取的字节数,如果读到文
// 件末尾,则返回0。
ssize_t write(int fd, const void *buf, size_t count);
// 从buf缓冲中读取count字节的数据并写入fd文件描述符所指代的文件中,函数返回实际写入文件的字节数。
int close(int fd);
//关闭文件描述符fd,此函数会释放文件描述符fd以及与之相关的内核资源。

以下は、上記の 4 つの関数の実際の応用例を示す、単純な cp コマンドのソース コードです。

/*
 * file : main.c
 * 
 */

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
#define BUF_SIZE 1024
#endif

int main(int argc, char *argv[])
{
    
    
    int inputFd, outputFd, openFlags;
    mode_t filePerms;
    ssize_t numRead;
    char buf[BUF_SIZE];
    
    if (argc != 3 || strcmp(argv[1], "--help") == 0)
    {
    
    
        printf("[usage]: %s old-file now-file\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    /* open input and output files */
    
    inputFd = open(argv[1], O_RDONLY);
    if (inputFd == -1)
    {
    
    
        printf("[error]: opening file %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    
    openFlags = O_CREAT | O_WRONLY | O_TRUNC; /* O_TRUNC clear file content */
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
        		S_IROTH | S_IWOTH; /* rw-rw-rw */
    outputFd = open(argv[2], openFlags, filePerms);
    if (outputFd == -1)
    {
    
    
        printf("[error]: opening file %s\n", argv[2]);
        exit(EXIT_FAILURE);
    }
    
    /* transfer data until we encounter end of input or an error */
    
    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
    {
    
    
        if (write(outputFd, buf, numRead) != numRead)
        {
    
    
            printf("[error]: couldn't write whole buffer\n");
        	exit(EXIT_FAILURE);
        }
    }
    
    if (numRead == -1)
    {
    
    
        printf("[error]: read\n");
        exit(EXIT_FAILURE);
    }
    
    if (close(inputFd) == -1)
    {
    
    
        printf("[error]: close input\n");
        exit(EXIT_FAILURE);
    }
    
    if (close(outputFd) == -1)
    {
    
    
        printf("[error]: close output\n");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}

通用I/O

UNIX I/O モデルの注目すべき機能の 1 つは、入出力の普遍的な概念です。これはよくファイルと呼ばれるものであり、上記のシステム I/O 呼び出し関数を使用して I/O 操作を実行できます。たとえば、上記のサンプル コードを次のように使用できます。

// 假设代码编译成可执行文件cp1,并且cp1位于当前目录下
./cp1 /dev/tty 1.txt // 从终端读取输入并写入文件1.txt
./cp1 1.txt /dev/tty // 将文件1.txt的内容打印到终端

その他の共通機能

lseek()

off_t lseek(int fd, off_t offset, int whence);
// 将fd文件描述符的读写指针从whence所描述的位置为起点,移动offset个字节。
// whence可以为SEEK_SET(文件开头),SEEK_CUR(当前位置),SEEK_END(文件结尾后的第一个字节)
// 函数返回调整后文件读写指针所指位置相对文件开头的偏移值

//常用使用技巧

lseek(fd, 0, SEEK_SET);   		/* Start of file */
lseek(fd, 0, SEEK_END); 		/* Next byte after the end of the file */
lseek(fd, -1, SEEK_END); 		/* Last byte of file */
lseek(fd, -10, SEEK_CUR); 		/* Ten bytes prior to current location */
lseek(fd, 10000, SEEK_END);		/* 10001 bytes past last byte of file */

画像-20220320211322226

この関数は、ファイル記述子のファイル読み取りおよび書き込みポインターのオフセットを変更するために使用されます。開いているファイルごとに、システム カーネルはそのファイル オフセットを次の write() または read() 操作のファイルの開始位置として記録します。

lseek はすべての種類のファイルでは機能しません。また、lseek() はパイプ、FIFO、ソケット、または端末では機能しません。上記のファイルとともに使用すると、呼び出しは失敗し、errno グローバル変数が ESPIPE に設定されます。

プログラムのファイル オフセットがファイルの終わりを超えて I/O 操作を実行すると、read() 関数はファイルの終わりを示す 0 を返しますが、write() 関数は任意の位置にデータを書き込むことができます。ファイルの終端以降、** ファイルの終端から新たに書き込まれるデータまでの空間をファイルホールと呼びます。**プログラムの場合、このスペースは 0 で埋められたファイルの内容ですが、ディスクの場合、このスペースはディスクのスペースを占有しません (そのため、ファイルのサイズがディスクの占有スペースよりも大きくなる場合があります)。

以下は、lseek と読み取りおよび書き込みの併用を示すサンプル プログラムです。『UNIX システム プログラミング マニュアル』のルーチンでは、私が作成した多くのライブラリ関数が使用されています。直接実行できるようにそれらを置き換えただけです。ソースコードをダウンロードする必要がありますが、もちろん、それほど「堅牢」ではありません。

/*
 * file : lseek_io.c
 */

// 该程序的第一个命令行参数为要打开的文件名称
// 余下的参数则指定了在文件上执行的输入/输出操作。每个表示操作的参数都以一个字母开头,
// 紧跟操作相关的值(中间不需要空格分割)。
// s<offset> : 从文件开始检索到offset字节位置
// r<length> : 在当前文件偏移量处读取length字节内容并以文本形式显示
// R<length> : 在当前文件偏移量处读取length字节内容并以十六进制形式显示
// w<str>    : 在当前文件偏移量处向文件写入str指定的字符串

#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

void usageErr(const char *format, ...)
{
    
    
    va_list argList;
    fflush(stdout);
    fprintf(stderr, "Usage: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);
    exit(EXIT_FAILURE);
}

void errExit(const char *format, ...)
{
    
    
    va_list argList;
    fflush(stdout);
    fprintf(stderr, "Error: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);
    exit(EXIT_FAILURE);
}

size_t getLong(char *str)
{
    
    
    long unsigned num;
    if (sscanf(str, "%lu", &num) > 0)
    {
    
    
        return (size_t)num;
    }
    else
    {
    
    
        return 0;
    }
}

int main(int argc, char *argv[])
{
    
    
    size_t len;
    off_t offset;
    int fd, ap, j;
    char *buf;
    ssize_t numRead, numWritten;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
    {
    
    
        usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n", argv[0]);
    }

    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); /* rw-rw-rw- */
    if (fd == -1)
    {
    
    
        errExit("open");
    }

    for (ap = 2; ap < argc; ap++)
    {
    
    
        switch (argv[ap][0])
        {
    
    
        case 'r': /* Display bytes at current offset, as text */
        case 'R': /* Display bytes at current offset, as hex */
            len = getLong(&argv[ap][1]);
            buf = malloc(len);
            if (buf == NULL)
            {
    
    
                errExit("malloc");
            }

            numRead = read(fd, buf, len);
            if (numRead == -1)
            {
    
    
                errExit("read");
            }

            if (numRead == 0)
            {
    
    
                printf("%s: end-of-file\n", argv[ap]);
            }
            else
            {
    
    
                printf("%s :", argv[ap]);
                for (j = 0; j < numRead; j++)
                {
    
    
                    if (argv[ap][0] == 'r')
                    {
    
    
                        printf("%c", isprint((unsigned char)buf[j]) ? buf[j] : '?');
                    }
                    else
                    {
    
    
                        printf("%02x ", (unsigned int)buf[j]);
                    }
                }
                printf("\n");
            }

            free(buf);
            break;

        case 'w': /* write string at current offset */
            numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
            if (numWritten == -1)
            {
    
    
                errExit("write");
            }
            printf("%s : wrote %ld bytes\n", argv[ap], (long) numWritten);
            break;

        case 's': /* Change file offset */
            offset = getLong(&argv[ap][1]);
            if (lseek(fd, offset, SEEK_SET) == -1)
            {
    
    
                errExit("lseek");
            }
            printf("%s: seek succeeded\n", argv[ap]);
            break;

        default:
            fprintf(stderr, "Argument must start with [rRws]: %s\n", argv[ap]);
        }
    }

    exit(EXIT_SUCCESS);
}

次のシェル コマンド プロセスは、上記のサンプル プログラムの実行効果と空のファイル現象を示しています。

water@LAPTOP-Q3TGG09O:~/lseek_io$ touch file
water@LAPTOP-Q3TGG09O:~/lseek_io$ ./lseek_io file s100000 wabc
s100000: seek succeeded
wabc : wrote 3 bytes
water@LAPTOP-Q3TGG09O:~/lseek_io$ ls -l file
-rw-r--r-- 1 water water 100003 Mar 20 22:48 file
water@LAPTOP-Q3TGG09O:~/lseek_io$ ./lseek_io file s10000 R5
s10000: seek succeeded
R5 :00 00 00 00 00

ioctl()

int ioctl(int fd, unsigned long request, ...);
// 用于执行通用I/O模型之外的文件操作,比如底层设备需要进行一些通过标准I/O函数无法进行的特殊的配置,
// 则可以通过这个接口进行
// fd为文件描述符,request为特定的操作请求,后面会跟一个或多个操作相关的参数,也可能没有

fd 引数は、要求で指定された制御操作が実行されるデバイスまたはファイルのオープン ファイル記述子です。デバイス固有のヘッダー ファイルは、リクエスト引数で渡すことができる定数を定義します。標準の C の省略記号 (…) 表記で示されているように、ioctl() の 3 番目の引数 (argp とラベル付け) は、任意の型にすることができます。request 引数の値により、ioctl() は argp で期待される値のタイプを決定できます。通常、argp は
整数または構造体へのポインタです。未使用の場合もあります。

要約する

通常のファイル操作では、通常、最初に open() 関数を使用してファイルを開いてファイル記述子を取得し、次に read()/write()/lseek() 関数を使用して関連する操作を実行し、最後に close() を使用します。ファイルを閉じます。

一般的な I/O モデルに組み込まれていないすべてのデバイスおよびファイル操作には、ioctl() 関数を使用します。

おすすめ

転載: blog.csdn.net/lczdk/article/details/123624664