文件I/O(基础知识、打开文件、创建文件、关闭文件描述符、文件偏移、读、写、文件共享、文件的原子读写、复制文件描述符、文件数据同步、获取文件的元数据、改变已经打开文件的属性、文件截断)

(0)基础知识
    1.文件的读写操作都是从当前文件的偏移处开始的。
    2.文件偏移量保存在文件表中。
    3.每个进程都有一个文件表。
    4.同步IO与异步IO
        在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。
        在异步文件IO中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
    5.链接: 硬链接, 符号链接
        硬链接:
            1.默认情况下,ln产生硬链接。
            2.建立硬链接时,链接文件和被链接文件必须位于同一个文件系统中。
            3.不能建立指向目录的硬链接。
        符号链接:
            1.ln命令加上- s选项,则建立符号链接。
            2.可以链接任意一个文件或者目录
            3.可以在不同的文件系统中链接
            
(1)打开文件
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
        ·pathname :表示要打开的文件相对路径。
        ·mode :只在创建文件时需要,用于指定所创建文件的权限位
        ·flags :用于指示打开文件的选项,常用的有 O_RDONLY 、 O_WRONLY 和 O_RDWR 。
            注意:  O_RDWR ! =O_RDONLY|O_WRONLY        //  O_RDONLY 被定义为 0 , O_WRONLY 被定义为 1 ,而 O_RDWR 却被定义为 2 。
            1.open 是 glibc 的一个变参函数。
            ·O_APPEND :每次进行写操作时,内核都会先定位到文件尾,再执行写操作。
            ·O_ASYNC :使用异步 I/O 模式。
            ·O_CLOEXEC :在打开文件的时候,就为文件描述符设置 FD_CLOEXEC 标志。
                // 这里设置为FD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递给exec创建的新进程, 如果设置为fcntl(fd, F_SETFD, 0);
                    那么本fd将保持打开状态复制到exec创建的新进程中。
            ·O_CREAT :当文件不存在时,就创建文件。
            ·O_DIRECT :对该文件进行直接 I/O ,不使用 VFS Cache 。
            ·O_DIRECTORY :要求打开的路径必须是目录。
            ·O_EXCL :该标志用于确保是此次调用创建的文件,需要与 O_CREAT 同时使用;当文件已经存在时, open 函数会返回失败。
            ·O_LARGEFILE :表明文件为大文件。
            ·O_NOATIME :读取文件时,不更新文件最后的访问时间。
            ·O_NONBLOCK 、 O_NDELAY :将该文件描述符设置为非阻塞的(默认都是阻塞的)。
            ·O_SYNC :设置为 I/O 同步模式,每次进行写操作时都会将数据同步到磁盘,然后 write 才能返回。
            ·O_TRUNC :在打开文件的时候,将文件长度截断为 0 ,需要与 O_RDWR 或 O_WRONLY 同时使用。            
    int openat(int dirfd, const char *pathname, int flags);
    int openat(int dirfd, const char *pathname, int flags, mode_t mode);
        1.如果pathname是绝对路径,则dirfd参数没用。
        2.如果pathname是相对路径,并且dirfd的值不是AT_FDCWD,则pathname的参照物是相对于dirfd指向的目录,而不是进程的当前工作目录;
        3.如果pathname是相对路径,并且dirfd的值是AT_FDCWD,pathname则是相对于进程当前工作目录的相对路径,此时等同于open。

(2)创建文件
    int creat(const char *pathname, mode_t mode);
        1.若成功返回为只写打开的文件描述符; 若出错,返回-1
        2.等效于open(path, O_WRONLY | O_CREATE | O_TRUNC, mode);

(3)关闭文件描述符
    int close(int fd);
    1.close 用于关闭文件描述符。而文件描述符可以是普通文件,也可以是设备,还可以是 socket 。
    2.遗忘 close 造成的问题
        · 文件描述符始终没有被释放。
        · 用于文件管理的某些内存结构没有被释放。
        ·对于普通进程来说,即使应用忘记了关闭文件,当进程退出时, Linux 内核也会自动关闭文件,释放内存(详细过程见后文)。
        ·但是对于一个常驻进程来说,问题就变得严重了。    
    3.lsof查看打开了,但是尚未关闭的文件
    
   (4)文件偏移
    off_t lseek(int fd, off_t offset, int whence);
    0.如果fd是指向一个管道,FIFO或者网络套接字, lseek返回-1,置errno为ESPIPE
    1.该函数用于将 fd 的文件偏移量设置为以 whence 为起点,偏移为 offset 的位置。
    2. whence 可以为三个值: SEEK_SET 、 SEEK_CUR 和 SEEK_END ,分别表示为 “ 文件的起始位置 ” 、
        “ 文件的当前位置 ” 和 “ 文件的末尾 ” ,而 offset 的取值正负均可。
    3. lseek 执行成功后,会返回新的文件偏移量。出错返回-1。
    4.确定打开文件的当前偏移量
        off_t curpos;
        curpos = lseek(fd, 0, SEEK_CUR);
    5.文件偏移量可以大于文件的当前长度, 在这种情况下, 对该文件的下一次写将加长该文件。并在文件中
        构建一个空洞,这一点是允许的。位于文件中没有学过的直接都被设为0
    6.lseek函数只修改文件表项中的当前文件偏移量, 不进行任何IO操作
    7.文件空洞不占用磁盘空间

(5)读
    ssize_t read(int fd, void *buf, size_t count);
    1.返回读取到的字节数。 若已经到文件尾部,再次调用read时返回0; 若出错返回-1。
    2.有很多情况可使实际读到的字节数少于要求读的字节数:
        1.文件中的当前偏移位置到文件结尾 < 要读取的字节数, 返回实际读取的字节数
        2.文件当前偏移位置已经在文件尾部, 返回0。(不阻塞)
        3.从网络读时, 网络中的缓冲机制可能造成返回值小于所要求读的字节数
        4.读管道或者FIFO时。 如果包含的字节数 < 要读取的字节数, 那么read将只返回实际可用的字节数。
        5.某些面向记录的设备(如磁带)一次最多返回一个记录。
        6.读取过程中被信号中断时, 返回实际读取的字节数
        
(6)写
    ssize_t write(int fd, const void *buf, size_t count); 
    1.write 尝试从 buf 指向的地址,写入 count 个字节到文件描述符 fd 中,并返回成功写入的字节数,同时
        将文件偏移向前移动相同的字节数。 write 有可能写入比指定 count 少的字节数。    
    2.当多个进程同时写一个文件时,即使对 write 进行了锁保护,在进行串行写操作时,文件依然不可避免地会被写乱。根本原因就在于文件偏移量是进程级别的。      

(7)文件共享

        1.文件指针指向文件表项
        2.v节点指针指向v节点表项
        3.v_data指向i节点。
      
    1.每个进程在进程表中都有一个打开文件描述符表
    2.内核为所有打开的文件维持一种文件表
    3.每个打开的文件(设备)都有一个v节点
    4.unix环境编程60页图
    注意:
        1.可能有多个文件描述符指向同一文件表项
            1.dup
            2.fork后, 父进程打开的所有"文件描述符"都被复制到子进程中,父子进程每个相同的打开描述符
               共享一个文件表项

(8)文件的原子读写
    ssize_t pread(int fd, void *buf, size_t count, off_t offset);
    ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
                        
    1.pread 不会从文件表中获取当前偏移,而是直接使用用户传递的偏移量,并且在读取完毕后,不会更改当前文件的偏移量。
    2.pwrite 的实现与 pread 类似

(9)复制文件描述符    
    int dup(int oldfd);
        1.dup()系统调用使用编号最小的文件描述符创建文件描述符oldfd的副本。
        2.在成功返回之后,旧的和新的文件描述符可以互换使用。
        3.两个文件描述符共用同一个文件表,但是不共享文件描述符标志
            例如:如果通过在其中一个描述符上使用lseek修改文件偏移量,则另一个描述符的偏移量也会更改。
                  关闭重复描述符的close-on-exec标志    
        4.调用close关闭文件描述符时, oldfd和拷贝的newfd都需要手动关闭。
    int dup2(int oldfd, int newfd);
        1.dup2与dup执行同样的操作,newfd是用户传入的
        2.如果newfd和已经是另外一个文件打开的描述符, 那么会先将newfd所在的文件描述符关闭,然后执行拷贝工作
        3.如果newfd与oldfd相同, 那么dup2函数不执行任何操作,且返回newfd
    int dup3(int oldfd, int newfd, int flags);
        dup3与dup2不同点在于:
        1. 调用者仅仅可以通过在flags中指定O_CLOEXEC来强制为新文件描述符设置close-on-exec标志。
        2. 如果oldfd等于newfd,则dup3()将失败并显示错误EINVAL。

(10)文件数据同步
    void sync(void);
        1.只是将所有修改过的块缓冲区排入写队列, 然后就返回, 他并不等待实际写磁盘操作结束
        2.updata系统守护进程周期性的调用sync函数(30s)
    int fsync(int fd);
        1.fsync 只同步 fd 指定的文件,并且等待写磁盘操作结束才返回。
        2.同步更新文件数据
    int fdatasync(int fd);
        1.类似于fsync但他只影响文件的数据部分
        2.不会同步更新文件的属性

(11)获取文件的元数据
    int stat(const char *path, struct stat *buf);
    int fstat(int fd, struct stat *buf);
    int lstat(const char *path, struct stat *buf);
        这三个函数都可用于得到文件的基本信息。
        1.stat 得到路径 path 所指定的文件基本信息。
        2.fstat得到文件描述符 fd 指定文件的基本信息。
        3.如果path指向的是链接文件,lstat得到的是链接文件自己本身的基本信息而不是其指向文件的信息。
        struct stat {
            dev_t st_dev;             // 设备ID
            ino_t st_ino;             // 节点号
            mode_t st_mode;         // 包含
            nlink_t st_nlink;         // 硬链接数
            uid_t st_uid;             // 所有者用户ID
            gid_t st_gid;             // 所有者组ID
            dev_t st_rdev;             // 设备ID(如果是特殊的文件)
            off_t st_size;             // 总大小, 以字节为单位
            blksize_t st_blksize;   // 文件系统I/O的块大小
            blkcnt_t st_blocks;     // 分配了512B的块数
            time_t st_atime;         // 上次访问时间
            time_t st_mtime;         // 最后修改时间
            time_t st_ctime;         // 上次状态变更时间
        };

(12)改变已经打开文件的属性
    int fcntl(int fd, int cmd, ... /* arg */ );    
    提供了以下5个功能
    1.复制一个文件描述符        (F_DUPFD、F_DUPFD_CLOEXEC )
    2.获取设置文件描述符标志(F_GETFD、F_SETFD)
    3.获取设置文件状态标志  (F_GETFL、F_SETFL)
    4.获取设置异步I/O所有权 (F_GETOWN、F_SETOWN )
    5.获取设置记录锁           (F_SETLK、F_SETLKW、F_GETLK)

(13)文件截断
    int truncate(const char *path, off_t length);
    int ftruncate(int fd, off_t length);
        1. truncate 截断的是路径 path 指定的文件, ftruncate 截断的是 fd 引用的文件。
        2. length 可以大于文件本身的大小,这时文件长度将变为 length 的大小,扩充的内容均被填充为 0 。
        3.尽管 ftruncate使用的是文件描述符,但是其并不会更新当前文件的偏移。
    1.为什么需要文件截断?
        如果某个文件已经存在有5个字节12345, 这时我打开文件(偏移量为0), 写入33, 结束后文件为33345,这个结果显然不是我们想要的
    2.文件截断为0长度的方法。
        1.打开文件的同时,指定 O_TRUNC标志。
        2.truncate(path, 0)   ftruncate(fd,0)


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部