zoukankan      html  css  js  c++  java
  • UNIX高级环境编程(1)File I/O

    引言:

        Unix系统中主要的文件操作包括:

    • open
    • read
    • write
    • lseek
    • close

        unbuffered IO和standard I/O相对应,后面的章节我们会讨论这两者的区别。

        在讨论open函数的时候,会引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构。

    一,文件描述符

        对应内核来说,每一个打开的文件都对应一个非负整数。

        有三个特殊的文件描述符:

    • 0表示标准输入
    • 1表示标准输出
    • 2表示标准错误输出

        对于较新的内核来说(Linux3.2.0,Solaris10等),文件描述符的数量并没有明确的限制,受限于内存的大小。

    二,常用的几个文件操作函数

        常用的文件操作函数包括:open,read,write,lseek,close

    1 open和openat函数

    函数声明:

    #include <fcntl.h>
    int open (const char *path, int oflag, … /* mode_t mode */);
    int openat (int fd, const char *path, int oflag, … /* mode_t mode */);

    返回值:

        OK:文件描述符(非负整数)

        Error:-1

    注:参数列表中,“...”表示不同的系统和标准中,该处的参数可能不相同。

    参数说明:

        path:文件名

        oflag:打开创建文件的属性。

                   下面有五个必选的oflag参数值,这五个值有切只能选一个。另外还有若干个可选参数值,可以自行百度。

                    NewImage

    细节说明:

    由open和openat返回的文件描述符保证为未使用的最小的文件描述。有的应用利用这一特性,先关闭标准输入描述符0,就可以在标准输入描述0上打开文件。

    参数fd可以区分open和openat函数。其取值有三种可能:

    1. path表示一个绝对路径,则fd参数无用,openat的功能和open相同;
    2. path表示一个相对路径,则fd是一个文件描述符,指定了path在文件系统中的起始位置,fd为打开path父目录时获取的文件描述符;
    3. path表示一个相对路径,而fd的值为AT_FDCWD,这时,path的父目录为当前工作目录,openat和open的功能相同。

     openat函数解决了两个问题:

    • 在多线程条件下,默认各个线程的工作目录时相同的(当前工作目录),使用这个函数可以使得各个线程的指定不同的工作目录;
    • 提供了一种方法解决TOCTTOU(time-of-check-to-time-of-use) error。

    这里介绍一下TOCTTOU错误。该类错误指的是,程序是非常脆弱的(vulnerable)如果该程序调用了两个文件相关的函数,第二个函数依赖于第一个函数的结果。因为两个函数是非原子操作,被操作的文件可能被两个函数轮流操作(线程切换),导致第一个函数的结果出错,从而程序出错。

    2 creat函数

    函数声明:

    #include <fcntl.h>
    
    int creat(const char* path, mode_t mode);

     返回值:

    • OK:文件描述符(只写)
    • Error:-1 

     creat函数相当于下面这样调用open函数

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

    creat有一点不方便,因为它打开的文件描述符是只读的,如果希望写入之后读回,需要依次调用creat、close和open,才能实现。

    因此,在这种场景下,一个更好的打开文件的方法是像下面这样调用open函数:

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

    3 close函数

    函数声明:

    #include <unistd.h>
    
    int close(int fd);

    返回值:

    • 0 :OK
    • -1:Error

    关闭一个文件会释放所有当前进程加在该文件上的记录锁。

    4 lseek函数

    每一个打开的文件都有一个”当前文件偏移量(current file offset)“,该偏移量是一个非负整数,记录了从文件开始到当前位置的字节数。

    函数声明:

    #include <unistd.h>
    
    off_t lseek(int fd, off_t offset, int whence);

    参数说明:

    offset的作用取决于参数whence的值:

    • 如果whence的值是SEEK_SET,文件的偏移量设置为offset个字节;
    • 如果whence的值是SEEK_CUR,文件的偏移量设置为当前偏移量加上参数offset的值;
    • 如果whence的值是SEEK_END,文件的偏移量设置为文件长度加上参数offset的值,offset可以是正值或负值。

    细节说明:

    获取当前文件偏移量的方法:

    1 off_t currpos;
    2 
    3 currpos = lseek(fd, 0, SEEK_CUR);

    lseek只记录当前文件在内核中的偏移量,并不会引起任何的IO操作。返回的offset将会在后面的read或write函数中使用。

    偏移量可以比当前文件的长度大,这时,再调用write函数时,将扩展该文件的长度。这样的操作相当于在文件中建了一个洞,该洞范围内读时返回0。

     使用od命令可以看到文件中的hole

    NewImage

    4 read函数

    函数声明:

    #include <unistd.h>
    
    ssize_t read(int fd, void *buf, size_t nbytes);

    返回值:

    • 正整数:读入的字节数
    • 0:文件结尾
    • -1: error

    细节说明:

     在一些情况下,函数返回的字节数比指定的读入字节数要小,多数是因为读到了文件末尾,或者指定的读取位置中包含的字节数小于指定的读入字节数,这时,read返回的为可读到的字节数。

    5 write函数

    函数声明:

    #include <unistd.h>
    
    ssize_t write (int fd, const void *buf, size_t nbytes);

    返回值:

    • 非负整数:写入的字节数,OK
    • -1: Error

     返回值总是等于参数nbytes的值,否则就会报错。

    对于常规的文件来说,写操作总是从当前文件偏移量开始。

    三、小结

    简单地介绍了一下常用的文件IO操作,并介绍了一些使用上的细节,比较常规。

    下一篇讲介绍更多文件IO的特性,包括:dup,fcntl,sync,fsync和ioctl函数。。

    好久没写博客了,又第一次用mac下的一个博客软件写,不太熟悉,所以写的比较简单,以后会写的更努力。 

    参考资料:

    《Advanced Programming in the UNIX Envinronment 3rd》

  • 相关阅读:
    Vue 函数
    VUE 基础语法
    C# txt文件操作
    C# 添加应用程序包
    Js 倒计时跳转
    Redis集群(主从集群)(一)
    JAVA基础总结001(《JAVA核心技术》)
    Linux学习001——文件和用户
    Linux——ELK集群搭建
    Linux安装jdk
  • 原文地址:https://www.cnblogs.com/suzhou/p/4290045.html
Copyright © 2011-2022 走看看