zoukankan      html  css  js  c++  java
  • Unix环境编程之文件IO

     1.文件IO

        2.文件与目录

        3.进程

        4.多线程编程

        5.信号

        6.进程间通信

        学习linux编程,首先要学会使用shell,这里一些基础命令就不介绍了。这里唯一要提的一个shell命令就是man。man是任何一个开发者都应该学会经常使用的工具,使用man比去查看任何一本教材都要来的快速准确。man可以查看一下内容:

    1.一般命令(shell命令)
    2.系统调用(open write等直接陷入内核的函数)
    3.子函数(C函数库等不直接陷入内核的函数)
    4.特殊文件(/dev/zero等linux系统中有特殊用途的文件)
    5.文件格式(linux系统的配置文件格式 host.conf)
    6.游戏
    7.宏和地方传统定义(本地配置)
    8.维护命令(tcpdump等用来观察linux系统运行情况的命令)

        IO分为有缓冲IO和无缓冲IO两种,具体的区别可以见下图。不带缓冲的I/O,直接调用系统调用,速度快,如函数open(), read(), write()等。而带缓冲的I/O,在系统调用前采用一定的策略,速度慢,比不带缓冲的I/O安全,如fopen(), fread() fwrite()等。

          

        下面介绍文件IO中的基础函数。

        1.open函数

    open函数:调用它可以打开或者创建一个文件。

    #include <fcntl.h>  

    int open(const char *pathname, int flags)  

    int open(const char *pathname, int flags, mode_t mode)  

    如果失败,返回值为-1
    参数解析: pathname是要打开或者创建的文件名。flags  文件打开时候的选项, O_RDONLY以只读方式打开文件。O_WRONLY以只写方式打开文件。 O_RDWR以读、写方式打开文件。
    这三个选项是必选的!


    flags 可选选项:
    O_APPEND 以追加方式打开文件,每次写时都写在文件末尾。
    O_CREAT    如果文件不存在,则创建一个,存在则打开它。
    O_EXCL      与O_CREAT一起使用时,如果文件已经存在则返回出错。
    O_TRUNC   以只写或读写方式打开时,把文件截断为0
    O_DSYNC   每次write时,等待数据写到磁盘上。
    O_RSYNC    每次读时,等待相同部分先写到磁盘上。
    O_SYNC      每次write时,等到数据写到磁盘上并接更新文件属性。
    SYNC选项都会影响降低性能,有时候也取决于文件系统的实现。

    mode  只有创建文件时才使用此参数,指定文件的访问权限。模式有:
       S_IRWX[UGO]    可读 可写 可执行
       S_IR[USR GRP OTH]   可读
       S_IW[USR GRP OTH]   可写
       S_IX[USR GRP OTH]    可执行
       S_ISUID   设置用户ID
       S_ISGID   设置组ID

    U->user G->group  O->others

        2.creat函数

    creat  以只写方式创建一个文件,若文件已经存在,则把它截断为0

    #include <fcntl.h>  

    int creat(const char *pathname, mode_t mode)  


    参数解析:
    pathname  要创建的文件名称mode   跟open的第三个参数相同,可读,可写,可执行 。如果失败 ,返回值为-1
    creat函数等同于  open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)

        3.close函数

    close 关闭已经打开的文件,并释放文件描述符

    #include <unistd.h>  

    int close(int filedes)  


    参数解析:filedes 文件描述符,有open或者creat返回的非负整数。

    如果失败,返回值为-1
    当一个进程结束时,操作系统会自动释放该进程打开的所有文件。但还是推荐用close来关闭文件。
    lsof命令可以查看进程打开了那些文件。

        4.lseek函数

    lseek 用来定位当前文件偏移量,既你对文件操作从文件的那一部分开始。

    #include <unistd.h>  

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

    如果失败,返回值为-1,成功返回移动后的文件偏移量。
    参数解析:filedes 文件描述符。offset 必须与whence一同解析
        whence为   SEEK_SET, 则offset从文件的开头算起。
        whence为   SEEK_CUR, 则offset从当前位置算起,既新偏移量为当前偏移量加上offset
        whence为   SEEK_END, 则offset从文件末尾算起。
    可以通过lseek、write来快速创建一个大文件。

        5.read函数

    read 从当前文件偏移量处读入指定大小的文件内容

    #include <unistd.h>  

    ssize_t read(int filedes, void *buf, size_t nbytes)

    失败返回-1, 成功返回读入的字节数,到文件末尾返回0
    参数解析 filedes 文件描述符 ,有open返回。buf  读入文件内容存放的内存首地址。nbytes 要读取的字节数。
    实际读入的字节数可能会小于要求读入的字节数。比如文件只有所剩的字节数小于你要读入的字节数,读取fifo文件和网络套接字时都可能出现这种情况。 

        6.write函数

    write向一个文件写入一定字节的内容。

    #include <unistd.h>  

    ssize_t write(int filedes, const void * buff, size_t nbytes)  


    失败返回-1,成功返回实际写入的字节数。当磁盘满或者文件到达上限时可能写入失败。
    一般从当前文件偏移量出写入,但如果打开时使用了O_APPEND,那么无论当前文件偏移量在哪里,都会移动到文件末尾写入。

        以上都是文件IO最基本的几个函数,那么linux的IO是怎么实现的呢?内核使用了三种数据结构,来实现I/O 
        1. 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个 文  件描述符相关联的是:
             (a) 文件描述符标志。
             (b) 指向一个文件表项的指针。
        2. 内核为所有打开文件维持一张文件表。每个文件表项包含:
              (a) 文件状态标志(读、写、增写、同步等)。
              (b) 当前文件位移量。
              (c) 指向该文件v节点表项的指针。
        3. 每个打开文件(或设备)都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。对于大多数文件, v节点还包含了该文件的i节点(索引节点)。例如, i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等等

    如下图所示,内核中的数据结构

    两个文件各自打开同一个文件,它们拥有各自的文件表项,但共享v节点表。见下图所示

        什么是原子操作?

        A B两个进程以O_APPEND方式打开同一个文件。A 进程去写该文件,假设此时文件偏移量为1000,B进程同时去写该文件,此时由于A进程未写完,则B进程得到的文件偏移量仍为1000。最后B进程的内容可能会覆盖掉A进程写的内容。pread , pwrite是原子读写操作。相当于先把文件偏移量定位到offset,然后在进行读写。这都是一步完成,不存在竞争问题。

    #include <unistd.h>  

    ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset)  

    ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset)  


    返回值跟read和write一样。offset为文件偏移量。

        下面介绍一些文件IO中比较高级的函数。dup(),fcntl(),sync()等。

        1.dup函数

    dup/dup2用来复制一个已经存在的文件描述

    #include <unistd.h>  

    int dup(int filedes) ;  

    int dup2(int filedes, int filedes2) ;  


    失败返回-1,成功返回新文件描述符。filedes2是新文件描述符,如果已经打开则先关闭它。
    ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
    共享文件表项。

        2.fcntl函数

     fcntl 可以改变已经打开的描述符。

    #include <unistd.h>  

    #include <fcntl.h>  

    int fcntl(int fd, int cmd)  

    int fcntl(int fd, int cmd, long arg)  


     参数解析:
           第一个为已经打开的文件描述符
           第二个为要对文件描述采取的动作 
           F_DUPFD   复制一个文件描述,返回值为新描述符。
           F_GETFD/F_SETFD   目前只有FD_CLOEXEC一个,set时候会用到第三个参数。 
           F_GETFL / F_SETFL  得到或者设置目前的文件描述符属性,返回值为当前属性。设置时使用第三个参数。

        3.sync函数

    #include <unistd.h>  

    int fsync(int filedes) //把指定文件的数据和属性写入到磁盘。  

    int fdatasync(int filedes) //把指定文件的数据部分写到磁盘。  

    void sync(void) //把修改部分排入磁盘写队列,但并不意味着已经写入磁盘。  

        最后些一个测试程序,希望可以用到里面大多数函数,用于测试其功能。这个程序功能是打开一个文件,在里面写入hello world,然后调用dup函数复制一个文件描述符,随后调用lseek将偏移量设置到hello之后,最后读出文件内容world打印到终端显示。代码如下所示

    #include <stdio.h>  

    #include <unistd.h>  

    #include <stdlib.h>  

    #include <fcntl.h>  

    #include <string.h>  

    int main(void)  

    {  

       int fd, fdd, ret;  

       char str[]="hello world!";  

       char buf[10];  

       fd = open("file", O_RDWR|O_CREAT|O_TRUNC, 755);  

       if(fd < 0){  

           perror("open error");  

           exit(1);  

       }  

         

       ret = write(fd, str, sizeof(str));  

       if(ret != sizeof(str)){  

           perror("write error");  

           exit(1);  

       }  

         

       fdd = dup(fd);  

       if(ret == -1){  

           perror("dup error");  

           exit(1);  

       }  

         

       lseek(fdd, 6, SEEK_SET);  

       memset(buf,0,sizeof(buf));  

       ret = read(fdd, buf, sizeof(buf));  

       if(ret < 0){  

           perror("read error");  

           exit(1);  

       }  

       printf("%s/n",buf);  

       return 0;  

    }  

  • 相关阅读:
    JSON.parse(JSON.stringify()) 实现对对象的深拷贝
    Promise 多重链式调用
    qs.parse() 和 qs.stringfy() 之 传输数据秘籍
    js 递归思想 处理后台多维数组的数据 之 完美契合
    js 反转字符串的实现
    js 中的! 和 !! 的区别
    vue 权限管理深度探究
    Web 前端 中高难度问题(希望看完之后的你可以拿到Offer^v^)
    vue懒加载 路由 router 的编写(resolve)
    Efficiency in Shell
  • 原文地址:https://www.cnblogs.com/zendu/p/4990759.html
Copyright © 2011-2022 走看看