作者:henrystark
Blog: http://henrystark.blog.chinaunix.net/
日期:20140419
本文遵循CC協定:署名-非商業性使用-禁止演繹 2.5()。可以自由拷貝,轉載。但轉載請保持文檔的完整性,注明原作者及原連結。如有錯訛,煩請指出。
LinuxTCP shutdown和close系統調用
0.寫作目的
面試時被問及系統調用如何實作,這個問題不好說。往深處說,牽涉到NR……等中斷向量的實作【引 3】;往淺了說,就是系統提供的接口在核心代碼如何實作。我最開始說了printf和write系統調用的關系,說到一半接不下去了【注 1】。于是該說shutdown和close兩個系統調用。
1.引言
socket網絡程式設計中,常用這兩個系統調用,最主要的差別是:shutdown強制關閉套接字,close隻将引用計數減一。
2.功能和代碼
2.1 shutdown
準确的定義見【引 1】。該函數有三種關閉方式:單獨關閉讀(寫)、同時關閉讀寫。shutdown處理過程調用序列見【引 2】。shutdown不管引用計數,會直接關閉套接口。源碼如下:
linux/net/ipv4/tcp.c
void tcp_shutdown(struct sock *sk, int how)
{
if (!(how & SEND_SHUTDOWN))
return;
if ((1 << sk->sk_state) &
(TCPF_ESTABLISHED | TCPF_SYN_SENT |
TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
if (tcp_close_state(sk))
tcp_send_fin(sk);
}
}
從注釋中可以看到,這個函數主要負責關閉套接口的讀端。注意,這裡為了處理用位與的方式來判斷是否是關閉讀端,how變量已經經過了處理,見shutdown系統調用在套接口層的實作inet_shutdown。
linux/net/ipv4/af_inet.c
int inet_shutdown(struct socket *sock, int how)
{
struct sock *sk = sock->sk;
int err = 0;
how++;
if ((how & ~SHUTDOWN_MASK) || !how)
return -EINVAL;
………………………………………………………………………………………………………………………………………………………………………………
linux/include/net/sock.h
#define SHUTDOWN_MASK 3
#define RCV_SHUTDOWN 1
#define SEND_SHUTDOWN 2
問題是,讀端怎麼關閉?實際上,shutdown導緻程序丢棄沒有讀取的或者後續到達的資料。這會在其他tcp接收函數中做處理,如tcp_poll、tcp_recvmsg等。
2.2 close
close系統調用的減引用計數操作主要由release函數完成,該函數最後調用close函數處理資料并發送fin。
linux/net/ipv4/af_inet.c
int inet_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk) {
long timeout;
//以下兩個函數實作引用計數-1
sock_rps_reset_flow(sk);
ip_mc_drop_socket(sk);
timeout = 0;
if (sock_flag(sk, SOCK_LINGER) &&
!(current->flags & PF_EXITING))
timeout = sk->sk_lingertime;
sock->sk = NULL;
sk->sk_prot->close(sk, timeout); //這裡調用tcp_close()
}
return 0;
}
linux/net/ipv4/tcp.c
void tcp_close(struct sock *sk, long timeout)
{
……………………………………………………………………………………………………………………………………………………
if (data_was_unread) {
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
sk->sk_prot->disconnect(sk, 0);
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
} else if (tcp_close_state(sk)) {
tcp_send_fin(sk); //這裡發送fin
}
sk_stream_wait_close(sk, timeout);
adjudge_to_death:
state = sk->sk_state;
sock_hold(sk);
sock_orphan(sk);
release_sock(sk);
……………………………………………………………………………………………………………………………………………………………………………………………………
}
可以看到,shutdown和close兩個系統調用最後都使用了send_fin函數來終止連接配接。
3.系統調用的實作機制
【引 3】中有系統調用的詳細實作機制。在核心中定義系統調用編号,應用程式用軟中斷通知系統切換到核心态,傳遞參數。
注解:
【1】printf是庫函數,write是系統調用,關于系統調用和庫函數的差別,也很複雜,【引 3】中講了一部分,關于printf的實作細節參見http://blog.csdn.net/dog250/article/details/23000909。