探討read的返回值的三種狀況

本文的copyleft歸gfree.wind@gmail.com全部,使用GPL發佈,能夠自由拷貝,轉載。但轉載請保持文檔的完整性,註明原做者及原連接,嚴禁用於任何商業用途。
======================================================================================================
 
今天探討一個很看似簡單的API 「read」的返回值問題。read的返回值有哪幾個值?每一個值又是在什麼狀況下發生的?
 
先問一下男人吧:man 2 read
RETURN VALUE
       On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.  It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer  bytes  are  actually available  right  now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal.  On error, -1 is returned, and errno is set appropriately.  In this case it is left unspecified whether the file position (if any) changes.
 
從上面的描述中,read的返回值一共有三種狀況:
1. 大於0:成功讀取的字節數;
2. 等於0:到達文件尾;
3. -1:發生錯誤,經過errno肯定具體錯誤值。
Note:本次討論只限於阻塞的fd,不討論非阻塞的狀況。
 
經過這個man的介紹,看似read的應用很簡單,但真的是這樣嗎?莫忘了Linux中文件是一個很common的概念。它能夠是一個真實的文件,也能夠是一個socket,一個設備,等等。對於真實的文件,文件尾EOF是一個肯定的狀況。
 
那麼若是是一個socket,它的返回值什麼時候爲0呢?還有,在read的過程當中,若是被信號中斷,到底是返回-1,仍是返回一個正值或者0呢?當對端關閉後,是否socket還能夠讀取對端關閉socket前發送的數據呢?
 
爲了搞清楚socket的行爲,必需要研究一下對應的kernel的代碼。本次以unix域的TCP鏈接的socket爲例,來探討一下socket的行爲。
 
unix_stream_recvmsg爲unix域的TCP鏈接的socket的讀取函數:
  1. static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
  2.              struct msghdr *msg, size_t size,
  3.              int flags)
  4. {
  5.     struct sock_iocb *siocb = kiocb_to_siocb(iocb);
  6.     struct scm_cookie tmp_scm;
  7.     struct sock *sk = sock->sk;
  8.     struct unix_sock *u = unix_sk(sk);
  9.     struct sockaddr_un *sunaddr = msg->msg_name;
  10.     int copied = 0;
  11.     int check_creds = 0;
  12.     int target;
  13.     int err = 0;
  14.     long timeo;
  15.     err = -EINVAL;
  16.     if (sk->sk_state != TCP_ESTABLISHED)
  17.         goto out;
  18.     err = -EOPNOTSUPP;
  19.     if (flags&MSG_OOB)
  20.         goto out;
  21.     target = sock_rcvlowat(sk, flags&MSG_WAITALL, size);
  22.     timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT);
  23.     msg->msg_namelen = 0;
  24.     /* Lock the socket to prevent queue disordering
  25.      * while sleeps in memcpy_tomsg
  26.      */
  27.     if (!siocb->scm) {
  28.         siocb->scm = &tmp_scm;
  29.         memset(&tmp_scm, 0, sizeof(tmp_scm));
  30.     }
  31.     mutex_lock(&u->readlock);
  32.     do {
  33.         int chunk;
  34.         struct sk_buff *skb;
  35.         unix_state_lock(sk);
  36.         skb = skb_dequeue(&sk->sk_receive_queue);
  37.         if (skb == NULL) {
  38.             if (copied >= target)
  39.                 goto unlock;
  40.             /*
  41.              *    POSIX 1003.1g mandates this order.
  42.              */
  43.             err = sock_error(sk);
  44.             if (err)
  45.                 goto unlock;
  46.             if (sk->sk_shutdown & RCV_SHUTDOWN)
  47.                 goto unlock;
  48.             unix_state_unlock(sk);
  49.             err = -EAGAIN;
  50.             if (!timeo)
  51.                 break;
  52.             mutex_unlock(&u->readlock);
  53.             timeo = unix_stream_data_wait(sk, timeo);
  54.             if (signal_pending(current)) {
  55.                 err = sock_intr_errno(timeo);
  56.                 goto out;
  57.             }
  58.             mutex_lock(&u->readlock);
  59.             continue;
  60.  unlock:
  61.             unix_state_unlock(sk);
  62.             break;
  63.         }
  64.         unix_state_unlock(sk);
  65.         if (check_creds) {
  66.             /* Never glue messages from different writers */
  67.             if ((UNIXCB(skb).pid != siocb->scm->pid) ||
  68.              (UNIXCB(skb).cred != siocb->scm->cred)) {
  69.                 skb_queue_head(&sk->sk_receive_queue, skb);
  70.                 break;
  71.             }
  72.         } else {
  73.             /* Copy credentials */
  74.             scm_set_cred(siocb->scm, UNIXCB(skb).pid, UNIXCB(skb).cred);
  75.             check_creds = 1;
  76.         }
  77.         /* Copy address just once */
  78.         if (sunaddr) {
  79.             unix_copy_addr(msg, skb->sk);
  80.             sunaddr = NULL;
  81.         }
  82.         chunk = min_t(unsigned int, skb->len, size);
  83.         if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
  84.             skb_queue_head(&sk->sk_receive_queue, skb);
  85.             if (copied == 0)
  86.                 copied = -EFAULT;
  87.             break;
  88.         }
  89.         copied += chunk;
  90.         size -= chunk;
  91.         /* Mark read part of skb as used */
  92.         if (!(flags & MSG_PEEK)) {
  93.             skb_pull(skb, chunk);
  94.             if (UNIXCB(skb).fp)
  95.                 unix_detach_fds(siocb->scm, skb);
  96.             /* put the skb back if we didn't use it up.. */
  97.             if (skb->len) {
  98.                 skb_queue_head(&sk->sk_receive_queue, skb);
  99.                 break;
  100.             }
  101.             consume_skb(skb);
  102.             if (siocb->scm->fp)
  103.                 break;
  104.         } else {
  105.             /* It is questionable, see note in unix_dgram_recvmsg.
  106.              */
  107.             if (UNIXCB(skb).fp)
  108.                 siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);
  109.             /* put message back and return */
  110.             skb_queue_head(&sk->sk_receive_queue, skb);
  111.             break;
  112.         }
  113.     } while (size);
  114.     mutex_unlock(&u->readlock);
  115.     scm_recv(sock, msg, siocb->scm, flags);
  116. out:
  117.     return copied ? : err;
  118. }
在這個函數中只有一個出口:
  1. return copied ? : err;
copied在unix_stream_recvmsg爲已讀取的字節數。那麼這行代碼的意義就比較明顯了,當已讀取了必定數據,那麼read的返回值即爲讀取的字節數。當沒有讀取任何數據時,就返回err。
 
下面看前面提出的兩個問題:
1. 什麼時候返回值爲0;
2. 被信號中斷時,read的返回值是什麼?
3. 對端關閉後,是否能夠繼續讀取對端關閉前發送的數據呢?
 
先看第一個問題:什麼時候返回值爲0。這須要copied爲0,且err爲0。
  1.             err = sock_error(sk);
  2.             if (err)
  3.                 goto unlock;
  4.             if (sk->sk_shutdown & RCV_SHUTDOWN)
  5.                 goto unlock;
這幾行代碼告訴了咱們答案。首先這幾行代碼是在socket的receive queue沒有數據時,纔會運行到達的。當socket沒有錯誤時,會繼續堅持socket的RCV_SHUTDOWN標誌位,若是設置了該標誌位,則goto 到unlock,直至最後的return返回語句。此時,copied爲0時,err也會爲0.
而sk->sk_shutdown的標誌位會在兩種狀況下被設置,參見unix_shutdown函數。在unix_shutdown函數,由API close或者shutdown觸發,它不只設置了本端的sk_shutdown標誌位,還會設置對端相對應的sk_shutdown標誌位。因此不管是本端仍是對端調用shutdown或者close時,都有可能致使本端的read返回爲0。這裏之因此說可能致使,是由於shutdown時能夠指定shutdown的行爲,是關閉發送仍是接收。
 
第二個問題,被信號中斷時,返回值是什麼?
  1.             timeo = unix_stream_data_wait(sk, timeo);
  2.             if (signal_pending(current)) {
  3.                 err = sock_intr_errno(timeo);
  4.                 goto out;
  5.             }
這幾行代碼是這個問題的答案。這幾行代碼一樣是處於receive queue爲空的判斷中。那麼,這說明若是receive queue中已有數據,且大於要讀取的字節數,那麼在這個函數中,根本就不會去判斷是否有pending的信號,返回值爲讀取的字節數。若是receive queue中沒有足夠的數據,那麼就會運行到此處,檢查是否有pending的信號。當有信號pending時,這時的返回值就決定於copied的值。若是已讀取了一些字節,那麼就返回copied即已讀取了的字節數——小於咱們要去讀取的字節數。若是沒有讀取任何字節,那麼就返回-1,且errno爲EINTR。
 
第三個問題,對端關閉後,是否能夠讀取對端在關閉以前發送的數據。
從前面兩個問題以及第一個問題的答案。這個問題的答案也很明顯了。在unix_stream_recvmsg中,只要receive queue中有數據,那麼就不會去檢查是否sk_shutdown的標誌。因此答案是,即便對端關閉socket,本端仍能夠讀取對端在關閉以前發送的數據。
 
本文只探討了unix域的TCP鏈接的socket的讀取狀況。至於其它種類的socket的read行爲,我猜想Linux仍然會採起一致的行爲——有心人能夠去看代碼驗證一下。
相關文章
相關標籤/搜索