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    *

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




  • 相关阅读:
    Leetcode Binary Tree Preorder Traversal
    Leetcode Minimum Depth of Binary Tree
    Leetcode 148. Sort List
    Leetcode 61. Rotate List
    Leetcode 86. Partition List
    Leetcode 21. Merge Two Sorted Lists
    Leetcode 143. Reorder List
    J2EE项目应用开发过程中的易错点
    JNDI初认识
    奔腾的代码
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5132701.html
Copyright © 2011-2022 走看看