zoukankan      html  css  js  c++  java
  • 系统编程--文件IO

    1.文件描述符
      文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时候,内核向进程返回一个文件描述符,新打开文件返回文件描述符表中未使用的最小文件描述符。Unix系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与进程的标准输出相关联,文件描述符2与进程的标准出错相关联,在POSIX标准中,幻数0、1、2应当替换为符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO
    Linux最大打开文件描述符数 cat /proc/sys/fs/file-max 或者 ulimit -n

     
    2.open函数
    调要open函数可以打开或创建一个文件。
    函数原型
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
     
     pathname是需要打开的文件;
          oflag参数用来说明此函数的多个选项。用 O_RDONLY(只读),O_WRONLY(只写),O_REWR(读写)。这三个常量必须指定,有且只能指定一个,并且和下列多个常量进行或运算构成:
         O_APPEND        每次写时都追加到文件的尾端
          O_CREAT          若文件不存在则创建它,需要第三个参数为指定文件的访问权限
          O_EXCL            若同时指定O_CREAT,而文件存在则会出错,用此则可以测试文件是否存在,若不存在则创建这个文件
          O_TRUNC         如果此文件不存在,而且为只写或读写成功打开,则将其长度截短为0
          O_NOCTTY        若pathname指的是终端设备,则不将该设备分配为此进程的控制端
          O_NONBLOCK    非阻塞模式
          这些常量是可选择的
     
    注意open函数与C标准I/O库的fopen函数有些细微的区别:
      以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则文件不存在就出错返回。以w或w+方式fopen一个文件时,如果文件已存在就截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。第三个参数mode指定文件权限,可以用八进制数表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,文件权限由open的mode参数和当前进程的umask掩码共同决定。
     
    3.create函数
    函数原型
    #include <fcnt1.h>
    int create(const char *pathname,mode_t mode);
    此函数等效于
    int open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);

    4.close函数

    函数原型
    #include<fcnt1.h>
    int close (int filedes); //返回值若成功返回0不成功返回-1
    5.lseek函数
    可以用lseek显式地为一个打开的文件设置其偏移量。
    函数原型
    #include<unistd.h>
    off_t lseek(int fileds,off_t offset,int whence); //若成功则返回新的文件偏移量,若出错则返回-1
    参数whence可以置为下值
    SEEK_SET,则将该文件的偏移量设置为距文件开始出offset个字节
    SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可正可负
    SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负
     
    若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件的当前偏移量
    off_t currpos; currpos = lseek(fd,0,SEEK_CUR);
    这种方法也可以用来确定所涉及的文件是否可以设置偏移量,如果文件描述符引用的是一个管道、FIFO进而网络套接字,则lseek返回-1,并将errorn设置为ESPIPE。
     
    注意:
    1).lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作
    2).文件偏移量可以大于文件的当前长度,在这种情况下,对该文件下一次的写将加长该文件,并在文件构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被设为 0
    3).文件中的空洞并不要求在磁盘上占用存储区。
     
    6.read函数
    从打开的文件中读数据到buf
    #include <unistd.h>
    ssize_t read(int filedes,void *buf,size_t nbytes);
    返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错返回-1;
      注意这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。read函数返回时,返回值说明了buf中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于求读的字节数
     
    7.write函数
    调用write函数向打开的文件写数据
    #include <unistd.h>
    ssize_t write(int filedes,const void *buf,size_t nbytes);
    返回值:若成功则返回字节数,若出错返回-1
    注:write出错的一个常见原因是:磁盘已写满,或者超过了一个给定进程的文件长度限制
     
    阻塞和非阻塞
      读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
      阻塞(Block)这个概念:当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,Linux内核中,处于运行状态的进程分为两种情况:
      正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。
      就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
     
    阻塞读终端
    #include <unistd.h>
    #include <stdlib.h>
    int main(void)
    {
        char buf[10];
        int n;
        n = read(STDIN_FILENO, buf, 10);
        if (n < 0) {
            perror("read STDIN_FILENO");
            exit(1);
        }
        write(STDOUT_FILENO, buf, n);
        return 0;
    }        

     

    非阻塞读
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    #define MSG_TRY "try again
    "
    int main(void)
    {
        char buf[10];
        int fd, n;
        fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
        if(fd<0) {
            perror("open /dev/tty");
            exit(1);
        }
    tryagain:
        n = read(fd, buf, 10);
        if (n < 0) {
            if (errno == EAGAIN) {
            sleep(1);
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
            goto tryagain;
        }
        perror("read /dev/tty");
        exit(1);
    }
        write(STDOUT_FILENO, buf, n);
        close(fd);
        return 0;
    }        
    8.dup和dup2函数
    这两个函数都用来复制一个现存的文件描述符
    #include <unistd.h>
    int dup(int filedes);
    int dup2(int filedes,int filedes2);
    //filedes2参数指定新描述符的数值。如果filedes2已经打开,则先关闭。
    返回值:若成功返回新的文件描述符,若出错则返回-1

    dup(filedes);等效于fcntl(filedes, F_DUPFD, 0);
    dup2(filedes, filedes2);等效于
    close(filedes2);
    fcntl(filedes, F_DUPFD, filedes2);

    区别
    1).dup2是一个原子操作,而close及fcntl则包括两个函数调用,有可能在close和fcntl之间插入执行信号捕获函数
    2).dup2和fcntl有某些不同errorno

    9.sync,fsync ,fdatasync函数
    为了解决延迟写的问题,保证磁盘上实际文件系统和缓冲区高速缓存中内容的一致性。

    #include <unistd.h>
    int fsync(int filedes); // 只对由文件描述符filedes指定的单一文件其作用,并且等待写磁盘操作结束
    int fdatesync(int filedes); //功能和fsync差不多,但它还同步更新文件的属性
    void sync(void); //将所有修改过的快缓冲区排入写队列,然后返回,它并不等待实际写磁盘操作结束

    10.fcntl函数

    可以改变已打开的文件性质

    #include <unistd.h>
    #include <fcntl.h>
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    int fcntl(int fd, int cmd, struct flock *lock);

    第三个参数总是一个整数,与上面所示函数原型中的注释部分相对应。但是在作为记录锁用时,第三个参数则是指向一个结构的指针。
    fcntl函数有5种功能:
    1).复制一个现有的描述符(cmd=F_DUPFD).
    2).获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
    3).获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
    4).获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
    5).获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    #define MSG_TRY "try again
    "
    int main(void)
    {
        char buf[10];
        int n;
        int flags;
        flags = fcntl(STDIN_FILENO, F_GETFL);
        flags |= O_NONBLOCK;
        if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
            perror("fcntl");
            exit(1);
        }
    tryagain:
        n = read(STDIN_FILENO, buf, 10);
        if (n < 0) {
            if (errno == EAGAIN) {
            sleep(1);
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
            goto tryagain;
        }
           perror("read stdin");
            exit(1);
        }
          write(STDOUT_FILENO, buf, n);
        return 0;
    }    

    11.ioctl函数
      ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据。例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位通过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过ioctl设置。

    #include <unistd.h> /*System V*/
    #include <sys/ioctl.h> /*BSD and Linux*/
    #include <stropts.h> /*XSI STREAMS*/
    int ioctl(int filedes, int request, . . .);

    以下程序使用TIOCGWINSZ命令获得终端设备的窗口大小。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    int main(void)
    {
        struct winsize size;
        if (isatty(STDOUT_FILENO) == 0)
            exit(1);
            if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0) {
            perror("ioctl TIOCGWINSZ error");
            exit(1);
        }
        printf("%d rows, %d columns
    ", size.ws_row, size.ws_col);
        return 0;
    }

    12./dev/fd
    比较新的unix/linux系统都提供名为/dev/fd的目录,其中有文件0、1、2等文件,打开这些文件,相当于复制这些文件描述符
    fd=open("/dev/fd/0",mode);
    等价于
    fd=dup(0);
    文件描述符fd和0将共享一个文件表记录项。

    13.文件共享
    文件共享是指不同进程间打开文件的共享。内核通过三种数据结构来表示打开的文件,
    1).每个进程有个进程表项,表中是打开文件的描述符向量。每个向量包含了文件描述符标记和一个指向文件表项的指针;
    2).内核为所有打开的文件维护一个文件表,每个文件表项包括了a,文件状态标记,如读、写、添加、非阻塞等b,当前的文件偏移量c,指向文件v-node表项的指针;
    3).每个打开文件或设备都有一个v-node结构 ,包含文件类型和指向操作文件的函数的指针。

    当两个以上独立进程打开同一个文件实现文件共享时,内核维护不同的文件表项,就是两个以上进程表项中的文件表项指针指向不同的文件表项,而不同的文件表项中的v-node指针指向同一个v-node而实现文件共享。由于每个进程有自己的打开文件表项,所以有自己的文件打开状态以及文件偏移量。

    需要注意的是Linux没有将相关数据分为i节点和v节点,而是采用了一个独立于文件系统的i节点(通用i-node结构)和一个依赖于文件系统的i节点

  • 相关阅读:
    Nginx模块fastcgi_cache的几个注意点
    Nginx的fastcgi_cache
    Nginx Location 语法,与简单配置[转]
    nginx location 匹配顺序
    Amoeba基本配置
    LVS的DR模式配置
    Keepalived安装及初步使用
    HAProxy安装及初步使用
    lvs nginx HAProxy优缺点
    Redis安装及初步使用
  • 原文地址:https://www.cnblogs.com/tla001/p/6522527.html
Copyright © 2011-2022 走看看