Unix Domain Socket(基於 Linux-2.4.0)
Unix Domain Sockets使用
上一章介紹了Socket介面層的實現,接下來我們將會介紹具體的協議層實現,這一章將會介紹用於程序間通訊的 Unix Doamin Sockets
的實現。要使用 Unix Domain Sockets
需要在建立socket時為 family
引數傳入 AF_UNIX
,如下程式碼:
fd = socket(AF_UNIX, SOCK_STREAM, 0);
這樣就可以建立一個型別為 Unix Domain Sockets
的socket描述符,如果我們編寫的是服務端程式,那就需要在呼叫 bind()
函式時為其指定一個唯一的檔案路徑,客戶端就可以通過這個檔案路徑來連線服務端程式。唯一路徑是通過結構體 struct sockaddr_un
服務端:
...
int fd;
struct sockaddr_un addr;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/server.sock");
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
...
客戶端:
... int fd; struct sockaddr_un addr; fd = socket(AF_UNIX, SOCK_STREAM, 0); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/tmp/server.sock"); connect(fd, (struct sockaddr *)&addr, sizeof(addr)); ...
Unix Domain Sockets實現
socket() 函式實現
上一章介紹過,在應用程式中呼叫 socket()
函式時候,最終會呼叫 sys_socket()
函式,sys_socket()
接著通過呼叫 sock_create()
函式建立socket物件,我們來看看 sock_create()
函式的實現:
int sock_create(int family, int type, int protocol, struct socket **res) { int i; struct socket *sock; ... if (!(sock = sock_alloc())) { printk(KERN_WARNING "socket: no more sockets\n"); i = -ENFILE; goto out; } sock->type = type; if ((i = net_families[family]->create(sock, protocol)) < 0) { sock_release(sock); goto out; } *res = sock; out: net_family_read_unlock(); return i; }
因為建立 Unix Domain Sockets
時需要為 family
引數傳入 AF_UNIX
,所以程式碼 net_families[family]->create()
就是呼叫 AF_UNIX
型別的 create()
函式。上一章也介紹過,需要通過呼叫 sock_register()
函式向 net_families
註冊具體協議的建立函式,對於 Unix Domain Sockets
在系統初始化時會在 af_unix_init()
函式中註冊其建立函式,程式碼如下:
struct net_proto_family unix_family_ops = {
PF_UNIX,
unix_create
};
static int __init af_unix_init(void)
{
...
sock_register(&unix_family_ops);
...
return 0;
}
所以對於程式碼 net_families[AF_UNIX]->create()
實際呼叫的就是 unix_create()
函式,unix_create()
函式實現如下:
static int unix_create(struct socket *sock, int protocol)
{
...
sock->state = SS_UNCONNECTED;
switch (sock->type) {
case SOCK_STREAM:
sock->ops = &unix_stream_ops;
break;
case SOCK_RAW:
sock->type=SOCK_DGRAM;
case SOCK_DGRAM:
sock->ops = &unix_dgram_ops;
break;
default:
return -ESOCKTNOSUPPORT;
}
return unix_create1(sock) ? 0 : -ENOMEM;
}
從 unix_create()
函式的實現可知,當向引數 protocol
傳入 SOCK_STREAM
時,就把 sock 的 ops
欄位設定為 unix_stream_ops
,如果傳入的是 SOCK_RAW
,那麼就把 sock 的 ops
欄位設定為 unix_dgram_ops
。這兩個結構的定義如下:
struct proto_ops unix_stream_ops = {
family: PF_UNIX,
release: unix_release,
bind: unix_bind,
connect: unix_stream_connect,
socketpair: unix_socketpair,
accept: unix_accept,
getname: unix_getname,
poll: unix_poll,
ioctl: unix_ioctl,
listen: unix_listen,
shutdown: unix_shutdown,
setsockopt: sock_no_setsockopt,
getsockopt: sock_no_getsockopt,
sendmsg: unix_stream_sendmsg,
recvmsg: unix_stream_recvmsg,
mmap: sock_no_mmap,
};
struct proto_ops unix_dgram_ops = {
family: PF_UNIX,
release: unix_release,
bind: unix_bind,
connect: unix_dgram_connect,
socketpair: unix_socketpair,
accept: sock_no_accept,
getname: unix_getname,
poll: datagram_poll,
ioctl: unix_ioctl,
listen: sock_no_listen,
shutdown: unix_shutdown,
setsockopt: sock_no_setsockopt,
getsockopt: sock_no_getsockopt,
sendmsg: unix_dgram_sendmsg,
recvmsg: unix_dgram_recvmsg,
mmap: sock_no_mmap,
};
unix_create()
函式接著會呼叫 unix_create1()
來進行下一步建立工作,程式碼如下:
static struct sock * unix_create1(struct socket *sock)
{
struct sock *sk;
if (atomic_read(&unix_nr_socks) >= 2*files_stat.max_files)
return NULL;
MOD_INC_USE_COUNT;
sk = sk_alloc(PF_UNIX, GFP_KERNEL, 1);
if (!sk) {
MOD_DEC_USE_COUNT;
return NULL;
}
atomic_inc(&unix_nr_socks);
sock_init_data(sock,sk);
sk->write_space = unix_write_space;
sk->max_ack_backlog = sysctl_unix_max_dgram_qlen;
sk->destruct = unix_sock_destructor;
sk->protinfo.af_unix.dentry = NULL;
sk->protinfo.af_unix.mnt = NULL;
sk->protinfo.af_unix.lock = RW_LOCK_UNLOCKED;
atomic_set(&sk->protinfo.af_unix.inflight, 0);
init_MUTEX(&sk->protinfo.af_unix.readsem);
init_waitqueue_head(&sk->protinfo.af_unix.peer_wait);
sk->protinfo.af_unix.list=NULL;
unix_insert_socket(&unix_sockets_unbound, sk);
return sk;
}
unix_create1()
函式主要是建立並初始化一個 struct sock
結構,然後儲存到 socket
物件的 sk
欄位中。這個 struct sock
結構就是 Unix Domain Sockets
的操作實體,也就是說所有對socket的操作最終都是作用於這個 struct sock
結構上。struct sock
結構的定義非常複雜,所以這裡就不把這個結構列出來,在分析的過程中涉及到這個結構的時候再加以說明。
bind() 函式實現
bind()
系統呼叫最終會呼叫 sys_bind()
函式,而 sys_bind()
函式繼而呼叫 unix_bind()
函式,呼叫鏈為: bind() -> sys_bind() -> unix_bind()
,我們來看看 unix_bind()
函式的實現:
static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
...
write_lock(&unix_table_lock);
if (!sunaddr->sun_path[0]) {
err = -EADDRINUSE;
if (__unix_find_socket_byname(sunaddr, addr_len,
sk->type, hash)) {
unix_release_addr(addr);
goto out_unlock;
}
list = &unix_socket_table[addr->hash];
} else {
// 根據檔案路徑查詢一個inode物件, 然後通過這個inode的編號查詢到合適的雜湊連結串列
list = &unix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)];
sk->protinfo.af_unix.dentry = nd.dentry;
sk->protinfo.af_unix.mnt = nd.mnt;
}
err = 0;
__unix_remove_socket(sk);
sk->protinfo.af_unix.addr = addr;
__unix_insert_socket(list, sk); // 把socket新增到全域性雜湊表中
out_unlock:
write_unlock(&unix_table_lock);
out_up:
up(&sk->protinfo.af_unix.readsem);
out:
return err;
...
}
unix_socket()
函式有一大部分程式碼是基於檔案系統的,主要就是根據 socket 繫結的檔案路徑建立一個 inode
物件,然後將這個 inode
的編號作為雜湊值找到 Unix Domain Sockets
全域性雜湊表對應的雜湊連結串列,最後把這個 socket 新增到雜湊連結串列中。通過這個操作,就可以把 socket 與一個檔案路徑關聯上。
listen() 函式
listen()
函式用於把 socket 設定為監聽狀態,listen()
函式首先會觸發 sys_listen()
函式,然後 sys_listen()
函式會呼叫 unix_listen()
函式把 socket 設定為監聽狀態,呼叫鏈為:listen() -> sys_listen() -> unix_listen()
,unix_listen()
函式的實現如下:
static int unix_listen(struct socket *sock, int backlog)
{
...
sk->max_ack_backlog = backlog;
sk->state = TCP_LISTEN;
...
return err;
}
unix_socket()
函式的實現很簡單,主要是把 socket 的 state
欄位設定為 TCP_LISTEN
,表示當前 socket 處於監聽狀態。同時還設定了 socket 的 max_ack_backlog
欄位,表示當前 socket 能夠接收最大使用者連線的佇列長度。