zoukankan      html  css  js  c++  java
  • 【Linux&Unix--open/close/write/read系统调用】

    个人学习整理。如有不足之处,请不吝不吝赐教。

    转载请注明:@CSU-Max


    系列博文:

                        

         Linux&Unix学习第二弹 -- exec 与 fock 系统调用

         Linux&Unix学习第三弹 -- open/close/write/read系统调用

          

           在 Unix/Linux 系统中,文件是一个非常重要的概念,本文将介绍 Linux 中和文件相关的几个重要的系统调用--open-close-write-read 系统调用。


    open系统调用

    函数原型及解释

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//open -- 打开或创建文件
    int open (
        const char *path,  /*pathname*/
        int flags,         /*flags*/
        mode_t perms       /*permissions (when creating)*/
    ) ;</span></span>

       调用 open能够打开一个已经存在的文件(普通文件、特殊文件或命名管道),或创建一个新文件。但它仅仅能创建普通文件(创建特殊文件须要使用 mknod,命名管道使用 mkfifo)。open返回是打开已存在的文件或创建新文件的文件描写叙述符。文件一旦打开,read write lseek close以及其它调用就能够使用其返回的文件描写叙述符。

     

    打开已存在文件

       首先我们来看一下 open函数的三个參数。path是已经存在的文件的路径;至于 flags參数。若值为 O_RDONLY ,就以仅仅读方式打开文件。若值为 O_WDONLY,就以仅仅写方式打开文件,若值为 O_RDWR,就以读写方式打开文件;而对于一个已经存在的文件,參数 perms是没实用的。通常将其省略,因此此种情况下 open调用仅仅需两个參数。

       open失败的原因非常多,常见的有例如以下两种:

       1.没有对应的文件訪问权限

       2.路径所指向的文件不存在

     

    创建新文件

       前面已经说到。当文件不存在时,open会创建一个新文件(仅能是普通文件),我们仅仅须要用 or操作向 open flags參数中增加标志 O_CREAT就可以。这样能够创建一个新的仅仅读文件,可是这没有不论什么意义,由于所创建的新文件没有不论什么可读内容。

    因此一般须要 O_CREAT O_WRONLY O_RDWR一起使用。此时就须要 perms參数了。

       比如:

          ec_neg1( fd = open(“/home/marc/newfile”,O_RDWR | O_CREAT, PERM_FILE) )

       參数 perms仅在创建新文件时有效,对于一个已经存在的文件。它没有不论什么作用。

       用户有时须要一个新的、没有不论什么数据的文件,即当文件已经存在,须要将其全部数据清除,并设置文件偏移量为0。标志 O_TRUNC能够实现此功能:

          ec_neg1( fd = open(“/home/marc/newfile”,O_WRONLY | O_CREAT | O_TRUNC, PERM_FILE) )

       由于 O_TRUNC可以破坏数据。所以仅仅要进程具有写权限,就行清除已存在文件的数据。由于它是写形式的一种。但对于具有 O_RDONLY标志的文件。它就不起作用了。

       O_WRONLY| O_CREAT | O_TRUNC这个组合是非经常见的(创建或截短一个具有仅仅写权限的文件),也有专门的相关的系统调用,即 creat系统调用。

     

    creat系统调用

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//creat -- 创建或清空文件
    int creat (
        const char *path, /*pathname*/
        mode_t perms      /*permissions*/
    ); </span></span>

       採用 open打开一个已经存在的文件仅仅须要第一个參数和第二个參数(path flags)。使用 creat创建新文件仅须要第一个參数和第三个參数(path perms)。实际上, creat不过一个宏:

       #define creat(path, perms) open(path,O_WRONLY | O_CREAT |O_TRUNC, perms)

       当然我们也能够仅使用open 而放弃使用 creat,可是在早期,open仅仅有两个參数,那时creat则无可替代。

     

    关于openflags參数

       除了以上介绍的 open标志外,open还有很多标志。详细的例如以下表所看到的:

     

    标志

    解释

    O_RDONLY

    仅仅读方式打开

    O_WRONLY

    仅仅写方式打开

    O_RDWR

    读写方式打开

    O_APPEND

    每次写都追加到文件的尾端

    O_CREAT

    若文件不存在则创建文件

    O_DSYNC

    设置同步I/O方式

    O_EXCL

    假设文件已存在,则出错;必须与O_CREAT一起使用

    O_NOCTTY

    不将此设备作为控制终端

    O_NONBLOCK

    不等待命名管道或特殊文件准备好

    O_RSYNC

    设置同步I/O方式

    O_SYNC

    设置同步I/O方式

    O_TRUNC

    将其长度截短为0

     

    close系统调用

    函数原型及解释

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//close -- 关闭文件描写叙述符 
    int close (
        int fd               /*file descriptor*/
    );</span></span>


       通过对 close进行分析,我们会发现close并没有做什么实质工作,它没有刷新不论什么内核缓冲区,而不过使文件描写叙述符能够重用。

       如上图,当指向一个打开文件描写叙述的全部文件描写叙述都关闭时。将删除该打开文件描写叙述。

    相同的。当指向一个信息节点的全部打开文件描写叙述都被删除时,将删除该内存信息节点。

     

    write系统调用

    函数原型及解释

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//write -- 向文件描写叙述符写
    ssize_t write (
        int fd,              /*file descriptor*/
        const void *buf,     /*data to write*/
        size_t nbytes        /*amount to write*/    
    ); </span></span>

       write系统调用将 buf所指向的缓冲区的 n字节写入 fd 所描写叙述ude打开文件里。

       写操作从文件偏移量的当前位置開始运行。而且在完毕之后,文件偏移量将添加所写入的字节数。若写入成功,返回值为已写入的字节数,出错则为 -1

       若设置了O_APPEND标志,写入前文件偏移量自己主动定位到文件的结尾。

       本文仅讨论普通文件的写操作。

    注:write也用与向管道,特殊文件和套接字写入数据,可是情况会有些不同,这些写操作能够堵塞(如它们可能正在等待可用数据)。假设堵塞了写操作,那么到达的信号会中断其操作。

    这样的情况下写操作将返回-1。并将 errno设置为 EINTR

     

    write的真面目

       看了前面的介绍,write似乎仅仅是写入数据,接着返回结果。

    实际上并不是如此。

    当用户调用 write系统调用时并不运行写操作,紧接着返回数据。它不过将数据传递给内核的缓冲区。

       当接收到 write请求时,先确保传入的文件描写叙述符能够使用,接着将数据拷贝到内核中的缓冲区。以后。在某个方便的时候,系统会设法把这部分的数据写入磁盘中。

    若发现错误,就会设法在控制台输出错误,可是该进程不会返回这个错误(可能此时其已经终止执行了)。若在系统向磁盘写出这些数据之前,该进程或其它进程试图要读取这些数据,那么系统将从内核缓冲区为你读取这些数据。

       总而言之。该进程不知道系统什么时候完毕了请求。也不知道是否完毕了请求。假设在该部分缓冲区的数据写出磁盘之前,有磁盘错误。或者因为某种原因内核停止了,那么你会发现要写的数据根本没有写到磁盘上,即使 write 没有报错。

       由此我们能够看出,这是一种延迟写。那么延迟写有哪些我们须要关心的问题呢?

       1.不能确定什么时候发生物理写操作

       2.一个调用写操作的进程没有得到写错误的通知

       3.物理写操作的顺序是无法操作的

     

    writeall函数

       writeall是一个很方便有用的函数。当须要确保写入全部的内容时,能够使用此函数。

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//writeall
    ssize_t writeall(int fd, const void *buf, size_t nbyte)
    {
            ssize_t nwritten = 0;       //总共写出的字符数 
            sszie_t n;                  //每次 write 操作写出的字符数 
            
            do{
                    if((n = write(fd, &((const char *)buf)[nwriten], nbyte - nwritten)) == -1)
                    {
                          if(errno == EINTR)        //堵塞时持续循环写出 
                              continue;
                          else
                              return -1;
                    }
                    nwritten += n;
             }while(nwritten < nbyte);
             return nwritten;
    }</span></span>

     

    read系统调用

    函数原型及解释

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//read -- 从文件描写叙述符中读入
    ssize_t read (
        int fd,              /*file descriptor*/
        void *buf,           /*address to receive data*/
        size_t nbytes        /*amount to read*/    
    ); </span></span>

       read系统调用与 write相反,它是从 fd所描写叙述的打开文件里读取 buf所指缓冲区中的 n个字节。read从当前文件偏移量開始读数据,而且完毕读操作后,文件偏移量将添加所读字节数。read的返回值是所读字节数、文件结束标志或者错误标志-1

    读操作不受 O_APPEND标志的影响。

       假设数据已经不在缓冲区中(因为曾经的I/O操作),进程必须等待内核从磁盘得到数据。

        write一样,从管道、特殊文件或套接字中读取数据时,read能够堵塞。

    此时读操作可能会被信号中断,结果返回值为-1,并把 errno 设置成 EINTR

     

    readall函数

       类比 writeall函数,假设须要读全部的数据,则通过循环调用 readreadall函数就是这种一个很方便有用的函数。

    <span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//readall
    ssize_t readall(int fd, const void *buf, size_t nbyte)
    {
            ssize_t nread == 0;        //总共读取的字符数 
            ssize_t n;                 //每次 read 操作读取的字符数 
            
       
            do{
                    if((n = read(fd, &((const char *)buf)[nread], nbyte - nread)) == -1)
                    {
                          if(errno == EINTR)        //堵塞时持续循环读取 
                              continue;
                          else
                              return -1;
                    }
                    nread += n;
             }while(nread < nbyte);
             return nread;
    }</span></span>

                                                                            

       ****************************************************************

       *   转载请注明出处:  @CSU-Max    http://blog.csdn.net/csu_max    *

       ****************************************************************                                                                  




  • 相关阅读:
    小熊派4G开发板初体验
    空间换时间,查表法的经典例子
    C语言、嵌入式应用:TCP通信实践
    【实践】基于RT-Thread的智慧路灯案例实验分享
    STM32串口打印的那些知识
    【RT-Thread笔记】BH1750软件包的使用
    【RT-Thread笔记】OneNet软件包的使用
    串口通讯你真的会了吗?不妨看看这些经验
    三小记(2)
    audio标签实现简单的自定义播放器
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5132701.html
Copyright © 2011-2022 走看看