1. 程式人生 > 其它 >Unix Domain Socket(基於 Linux-2.4.0)

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 能夠接收最大使用者連線的佇列長度。