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

作者:[email protected]
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft歸[email protected]所有,使用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 *= 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仍然會採取一致的行爲——有心人可以去看代碼驗證一下。
發佈了12 篇原創文章 · 獲贊 23 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章