• linux学习之文件I/O篇(一)


    文件I/O

    1.C标准函数与系统函数的区别1.c标准函数和系统函数的区别

      接下来用应用层API的知识。每当打开一个文件,默认打开标准输入,标准输出,标准出错流三个流,每个FILE都对应一个缓冲区,默认大小为8192Byte。

     2.PCB概念(进程控制块)

    (1)task_struck结构体

      可以自己动手查看  vi  /usr/src/linux-headers/includelinuxsched.h

      在linux中,把每一个进程的基本信息抽象成一个结构体,这就是task_struct结构体,在includelinuxsched.h文件中定义。每个进程都会被分配一个task_struct结构,它包含了这个进程的所有信息。在任何时候,操作系统都能跟踪这个结构的信息。

    struct task_struct {
    volatile long state;
    / /这个是进程的运行时状态,-1代表不可运行,0代表可运行,>0代表已停止。
    可运行状态 TASK_RUNNING
    处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序决定。

    等待状态 TASK_INTERRUPTIBLE可中断 TASK_UNINTERRUPTIBLE不可中断 
    处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态和不可中断的等待状态。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。

    暂停状态TASK_STOPPED
    此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。

    僵死状态TASK_ZOMBIE
    进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。

    (2)files struck 每个PCB里边有一个,本质是一个数组。

    (3)open/close

      转自:http://joe.is-programmer.com/posts/17463.html

      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参数是要打开或创建的文件名,和fopen一样,pathname既可以是相对路径也可以是绝对路径。

    flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示or

    必选项:以下三个常数中必须指定一个,且仅允许指定一个。
    O_RDONLY 只读打开
    O_WRONLY 只写打开
    O_RDWR 可读可写打开

    以下可选项可以同时指定0个或多个,和必选项按位或起来作为flags参数。
    O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
    O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。
    O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
    O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节。
    O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/O)。


    第三个参数mode指定文件权限,可以用八进制数表示,比如0644表示-rw-r--r--,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,文件权限由open的mode参数和当前进程的umask掩码共同决定。注意:创建文件权限不能大于执行程序用户的自有权限,如O_CREAT选项时,忘加mode参数,则新创建的文件从栈帧中取出垃圾值为mode。
    close函数关闭一个已打开的文件:

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

      返回值:成功返回0,出错返回-1并设置errno

      参数fd是要关闭的文件描述符。需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。


    给个例子:

    复制代码
    //filename:code
    //
    #include <error.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include<stdio.h>
    #include<unistd.h>
    int main(void) { int fd;
    char buf[1024]="helloworld";
    fd=open("abc",O_CREAT|O_RDWR,0777);
    write(fd,buf,strlen(buf));
    printf("fd=%d ",fd);
    close(fd);
    return 0; }

    执行:gcc open.c -o app 查看结果:$umask 0002 $ls -l abc -rwxrwxr–x 1 joseph joseph 0 11月7日 10:24 abc 以上例子以mode 0777创建一个文件mytest,而umask是0002, 所以创建的文件权限是0777-0002=0775(-rwxrwxr–x)。

    最大打开文件个数

    一般默认为1024,查看方式  

    ulimit -a

    其中的open files 就是,改变方式 ulimit -n 文件数,最多能打开个个数,查看方式  cat /proc/sys/fs/file-max,数目与内存的大小有关

    (4)read/write

    read函数从打开的设备或文件中读取数据。

    #include <unistd.h>

    ssize_t read(int fd, void *buf, size_t count);

    返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0

    参数count是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。注意这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。read函数返回时,返回值说明了buf中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count,例如:

    1、读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字节而请求读100个字节,则read返回30,下次read将返回0。

    2、从终端设备读,通常以行为单位,读到换行符就返回了。

    3、从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数。

    write函数向打开的设备或文件中写数据。
    #include <unistd.h>

    ssize_t write(int fd, const void *buf, size_t count);

    返回值:成功返回写入的字节数,出错返回-1并设置errno

    写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一定。

    读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

    实例:

     1 #include<unistd.h>
     2 #include<sys/stat.h>
     3 #include<sys/types.h>
     4 #include<fcntl.h>
     5 #include<stdlib.h>
     6 #include<strio.h>
     7 
     8 #define SIZE 8192
     9 
    10 int main(int argc,char *argv[])
    11 {
    12        char buf[SIZE];
    13        int  fd_src,fd_dest,len;
    14        if(argc<3)
    15        {
    16               printf("./mycp src dest
    ");
    17               exit(1);
    18        }
    19         fd_src=open(argv[1],O_RDONLY);
    20           fd_dest=open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,00644);
    21 
    22        while((len=read(fd_src,buf,sizeof(buf))>0)
    23        {
    24                    write(fd_dest,buf,len);
    25         }
    26         close(fd_src);
    27         close(fd_dest);
    28         return 0;
    29 }

    (5)阻塞和非阻塞(阻塞相当于c++中的cin,一直等待键盘输入)

    转自http://blog.csdn.net/u012317833/article/details/39343915

    阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:

    • 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

    • 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

    下面这个小程序从终端读数据再写回终端。

    阻塞读终端
    1. #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;  
      }  
    执行结果如下:
    1. [root@localhost apue2]# ./a.out  
      hello  
      hello  
      [root@localhost apue2]# ./a.out   
      hello world  
      hello worl[root@localhost apue2]# d  
      bash: d: command not found  
      [root@localhost apue2]#   

      第一次执行a.out的结果很正常,而第二次执行的过程有点特殊,现在分析一下:

    1. Shell进程创建a.out进程,a.out进程开始执行,而Shell进程睡眠等待a.out进程退出。

    2. a.out调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。

    3. a.out进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。

      如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备暂时没有数据可读就返回-1,同时置errnoEWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

     
    1. while(1) {  
          非阻塞read(设备1);  
          if(设备1有数据到达)  
              处理数据;  
          非阻塞read(设备2);  
          if(设备2有数据到达)  
              处理数据;  
          ...  
      }  

      如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。

    非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行

     
    1. while(1) {  
          非阻塞read(设备1);  
          if(设备1有数据到达)  
              处理数据;  
          非阻塞read(设备2);  
          if(设备2有数据到达)  
              处理数据;  
          ...  
          sleep(n);  //多出来的部分
      }  

      这样做的问题是,设备1有数据到达时可能不能及时处理,最长需延迟n秒才能处理,而且反复查询还是做了很多无用功。以后要学习的select(2)函数可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,从而圆满地解决了这个问题。

      以下是一个非阻塞I/O的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端来做这个实验。程序开始执行时在0、1、2文件描述符上自动打开的文件就是终端,但是没有O_NONBLOCK标志。读标准输入是阻塞的。我们可以重新打开一遍设备文件/dev/tty(表示当前终端),在打开时指定O_NONBLOCK标志。

    非阻塞读终端
     
    1. #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) { //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; }

      以下是用非阻塞I/O实现等待超时的例子。既保证了超时退出的逻辑又保证了有数据到达时处理延迟较小。

    非阻塞读终端和等待超时

     
      1. #include <unistd.h>  
        #include <fcntl.h>  
        #include <errno.h>  
        #include <string.h>  
        #include <stdlib.h>  
          
        #define MSG_TRY "try again
        "  
        #define MSG_TIMEOUT "timeout
        "  
          
        int main(void)  
        {  
            char buf[10];  
            int fd, n, i;  
            fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);  
            if(fd<0) {  
                perror("open /dev/tty");  
                exit(1);  
            }  
            for(i=0; i<5; i++) {  
                n = read(fd, buf, 10);  
                if(n>=0)  
                    break;  
                if(errno!=EAGAIN) {  
                    perror("read /dev/tty");  
                    exit(1);  
                }  
                sleep(1);  
                write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));  
            }  
            if(i==5)  
                write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));  
            else  
                write(STDOUT_FILENO, buf, n);  
            close(fd);  
            return 0;  
        }  
        注:error和perror
      2. perror会根据errno返回的序号,查找对应的出差信息并打印
  • 相关阅读:
    VB.NET中获取串口列表
    跟着你混,真吃亏!
    [翻译]部署Microsoft .NET Framework Version 3.0(含下载)
    将特定格式的TXT数据文件写入EXCEL
    VB.NET中判断一个数组中是否有重值
    多语言应用程序开发
    .NET 环境下进制间的转换
    初识.NET
    映射Y轴
    Culture Name
  • 原文地址:https://www.cnblogs.com/rainbow1122/p/7795703.html
Copyright © 2020-2023  润新知