《Linux/UNIX系統編程手冊》第56章 SOCKET:介紹


關鍵詞:

 socket是一種IPC方法,它允許位於同一主機或使用網絡連接起來的不同主機上的程序之間交換數據。

關於Socket及后續章節介紹Socket的用法:

1. socket基礎

一個典型的客戶端/服務器場景中,應用程序使用socket進行通信的方式如下:

  • 各個應用程序創建一個socket。socket是一個允許通信的設備,兩個應用程序都需要用到它。
  • 服務器將自己的socket綁定到一個眾所周知的地址上是的客戶端能夠定位到它的位置。

關鍵socket API包括以下下幾種:

  • socket()創建一個新的socket。
  • bind()將一個socket綁定到一個地址上。通常服務器需要使用這個調用來將其socket綁定到一個眾所周知的地址上使得客戶端能夠定位到該socket上。
  • listen()允許一個流socket接受來自其他socket的接入連接。
  • accept()在一個監聽流上接受來自一個對等應用程序的連接,並可選地返回對等socket的地址。
  • connect()建立與另一個socket之間的連接。
  • read()/write()/close()基於socket的讀寫和關閉。
  • recv()/send()/recvfrom()/sendto()分別通過socket發送或接收數據,類似read()/write()但是功能更豐富。

1.1 socket API介紹

1.1.1 socket domain

socket存在於一個通信domain中:識別出一個socket的方法(socket地址格式);通信范圍(是統一主機不同應用之間;還是一個網絡連接的不同主機上應用之間)。

domain都是以AF_開頭,表示Address Family;PF_開頭的表示Protocol Family。

在socket.h中定義如下,可以看出AF_和PF_基本一對一。

/* Protocol families.  */
#define PF_UNSPEC    0    /* Unspecified.  */
#define PF_LOCAL    1    /* Local to host (pipes and file-domain).  */
#define PF_UNIX        PF_LOCAL /* POSIX name for PF_LOCAL.  */
#define PF_FILE        PF_LOCAL /* Another non-standard name for PF_LOCAL.  */
#define PF_INET        2    /* IP protocol family.  */
#define PF_AX25        3    /* Amateur Radio AX.25.  */
#define PF_IPX        4    /* Novell Internet Protocol.  */
#define PF_APPLETALK    5    /* Appletalk DDP.  */
#define PF_NETROM    6    /* Amateur radio NetROM.  */
#define PF_BRIDGE    7    /* Multiprotocol bridge.  */
#define PF_ATMPVC    8    /* ATM PVCs.  */
#define PF_X25        9    /* Reserved for X.25 project.  */
#define PF_INET6    10    /* IP version 6.  */
...
#define PF_MAX        44    /* For now..  */

/* Address families.  */
#define AF_UNSPEC    PF_UNSPEC
#define AF_LOCAL    PF_LOCAL
#define AF_UNIX        PF_UNIX
#define AF_FILE        PF_FILE
#define AF_INET        PF_INET
#define AF_AX25        PF_AX25
#define AF_IPX        PF_IPX
#define AF_APPLETALK    PF_APPLETALK
#define AF_NETROM    PF_NETROM
#define AF_BRIDGE    PF_BRIDGE
#define AF_ATMPVC    PF_ATMPVC
#define AF_X25        PF_X25
#define AF_INET6    PF_INET6
...
#define AF_MAX        PF_MAX

常用的AF_有AF_UNIX、AF_INET、AF_INET6三種。

  • AF_UNIX domain允許在同一主機上的應用程序之間進行通信。
  • AF_INET domain允許在使用IPv4網絡連接起來的主機上的應用程序之間進行通信。
  • AF_INET6 domain允許在使用IPv6網絡連接起來的主機上的應用程序之間進行通信。

1.1.2 socket type

每個socket實現都至少提供了兩種socket:流和數據報。

/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,        /* Sequenced, reliable, connection-based
                   byte streams.  */
 SOCK_DGRAM = 2,        /* Connectionless, unreliable datagrams
                   of fixed maximum length.  */
  SOCK_RAW = 3,            /* Raw protocol interface.  */
  SOCK_RDM = 4,            /* Reliably-delivered messages.  */
  SOCK_SEQPACKET = 5,        /* Sequenced, reliable, connection-based,
                   datagrams of fixed maximum length.  */
  SOCK_DCCP = 6,        /* Datagram Congestion Control Protocol.  */
  SOCK_PACKET = 10,        /* Linux specific way of getting packets
                   at the dev level.  For writing rarp and
                   other similar things on the user level. */
  /* Flags to be ORed into the type parameter of socket and socketpair and
     used for the flags parameter of paccept.  */

  SOCK_CLOEXEC = 02000000,    /* Atomically set close-on-exec flag for the
                   new descriptor(s).  */
  SOCK_NONBLOCK = 00004000    /* Atomically mark descriptor(s) as
                   non-blocking.  */
};

流socket(SOCK_STREAM)提供了一個可靠的雙向的字節流通信信道。

流socket的正常工作需要一對相互連接的socket,因此流socket通常被稱為面向連接的。

 

數據報socket(SOCK_DGRAM)允許數據以被稱為數據報的消息的形式進行交換。

數據報socket是更一般的無連接socket概念,一個數據報socket在使用時無需與另一個socket連接。 

在Internet domain中,數據報socket使用UDP,流socke通則使用TCP。

1.1.3 struct sockaddr

各種socket domain使用了不同的地址格式,對於各種socket domain都需要定義一個不同的結構類型來存儲socket地址。

然而由於bind()調用適用於所有socket domain,因此他們必須要能夠接受任意類型的地址結構。

為此,socket API定義了一個通用的地址結構struct sockaddr。

這個類型的唯一用途是將各種domain特定的地址結構轉換成單個類型以供socket各個參數使用。

/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];        /* Address data.  */
  };

這個結構是所有domain特定的地址結構的模板,其中每個地址結構均以與sockadr結構中的sa_family打頭。

通過sa_family字段值足以確定存儲在這個結構的剩余部分中地址大小和格式了。

1.1.4 socket():創建一個socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
    Returns file descriptor on success, or –1 on error

domain指定了socket通信domain,常用的有AF_UNIX、AF_INET、AF_INET6;type指定了socket類型,常用的有SOCK_STREAM、SOCK_DGRAM;protocol一般被指為0。

socket()在成功時會返回一個引用在后續調用中會用到的新創建的socket文件描述符;錯誤則返回-1。

1.1.5 bind():將socket綁定到地址

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    Returns 0 on success, or –1 on error

sockfd是由socket()返回的文件描述符。

addr是指向指定socket綁定到的地址的結構體指針,傳輸參數的類型取決於socket domain。

addrlen參數指定了地址結構的大小。

其中addr傳入到內核,最終被不同socket domain的proto_ops->bind()調用的時候,會被強制轉換成不同數據結構。

比如AF_UNIX、AF_INET、AF_INET6對應的地址結構體分別為struct sockaddr_un、struct sockaddr_in、struct sockaddr_in6

#define UNIX_PATH_MAX    108

struct sockaddr_un {
    __kernel_sa_family_t sun_family; /* AF_UNIX */---------------------填入AF_UNIX。
    char sun_path[UNIX_PATH_MAX];    /* pathname */--------------------本地socket的路徑。
};

#define __SOCK_SIZE__    16        /* sizeof(struct sockaddr)    */
struct sockaddr_in {
  __kernel_sa_family_t    sin_family;    /* Address family        */---填入AF_INET。
  __be16        sin_port;    /* Port number            */--------------端口號。
  struct in_addr    sin_addr;    /* Internet address        */---------IPv4的地址。

  /* Pad to size of `struct sockaddr'. */
  unsigned char        __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero    __pad        /* for BSD UNIX comp. -FvK    */

struct sockaddr_in6 {
    unsigned short int    sin6_family;    /* AF_INET6 */---------------填入AF_INET6。
    __be16            sin6_port;      /* Transport layer port # */
    __be32            sin6_flowinfo;  /* IPv6 flow information */
    struct in6_addr        sin6_addr;      /* IPv6 address */
    __u32            sin6_scope_id;  /* scope id (new in RFC2553) */
};

1.1.6 listen():監聽接入連接

#include <sys/socket.h>
int listen(int sockfd, int backlog);
    Returns 0 on success, or –1 on error

listen()將sockfd引用的流socket標記為被動,這個socket后面會被用來接受來自其他socket連接。

如果客戶端在服務器調用accept()之前調用connect(),這將會產生一個未決的連接。

內核必須記錄所有未決的連接請求,在后續accept()就能夠處理這些請求。

backlog參數允許限制這種未決連接數量。在這個限制內的連接請求會立即成功。之外的連接請求就會阻塞直到一個未決連接被接受,並從未決隊列中刪除為止

1.1.7 accept():接收連接

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    Returns file descriptor on success, or –1 on error

accept()用在sockfd引用的監聽流socket上接受一個接入連接。

如果在accept()時不存在未決的連接,那么調用就會阻塞直到有連接請求到達為止

 

理解accept()的關鍵點是它會創建一個新socket,並且這是這個新socket會與執行connect()的對等socket進行連接

accept()返回結果是已經連接的socket文件描述符,其會保持打開狀態,並且可以被用來接受后續的連接

addr參數指向了一個用來返回socket地址的結構。

addrlen在調用之前必須要將其初始化為addr指向的緩沖區大小;返回之后被設置成實際被復制進緩沖區中的數據的字節數。

1.1.8 connect():(客戶端)連接到(服務器)對等socket

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    Returns 0 on success, or –1 on error

connect()將sockfd引用的socket連接到地址通過addr和addrlen指定的監聽socket上。

其中addr和addrlen參數指定方式與bind()對應參數指定方式相同。

1.1.9 sendto()/recvfrom():交換數據報

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
    Returns number of bytes received, 0 on EOF, or –1 on error
ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
    Returns number of bytes sent, or –1 on error

flags是一個位掩碼,控制着特定的IO特性。

src_addr和addrlen用來獲取或指定與之同行的對等socket地址。

對於recvfrom()來說,src_addr和addlen會返回用來發送數據報的遠程socket地址。如果不關心,可以將src_addr和addrlen都指定為NULL。

對於sendto()來說,dest_addr和addrlen制定了數據報發送到的socket地址。

1.1.10 read()/write()/close():讀、寫、關閉socket文件

如果作為客戶端,socket()返回的sockfd,在connect之后就就可以對sockfd進行read()/write()/close()操作。

如果作為服務端,在accept()之后產生新的sockfd,之后sockfd就保持打開狀態。可以對其進行read()/write()/close()操作。

1.2 流socket

 

1.3 數據報socket

 

2. 從socket API到系統調用socketcall()

上面介紹了一系列socket相關API,但是這些C函數並沒有對應的系統調用。

下面就看看這些scoket API是如何轉到內核調用的,以socket()為例。

int
__socket (int fd, int type, int domain)
{
#ifdef __ASSUME_SOCKET_SYSCALL
  return INLINE_SYSCALL (socket, 3, fd, type, domain);
#else
  return SOCKETCALL (socket, fd, type, domain);----------根據SOCKETCALL()定義可知,socket通過連接符號便變成其對應的call,作為socketcall()的第一個參數。 #endif
}
libc_hidden_def (__socket)-------------------------------對libc之外屏蔽__socket()函數訪問。
weak_alias (__socket, socket)----------------------------如果沒有定義socket()函數,那么對socket()的調用將會轉到調用__socket()。

2.1 SOCKETCALL():

從下面SOCKETCALL()宏定義可知,最終是通過socketcall()系統調用實現的。

具體對應connect()對應的是SOCKOP_socket,即socketcall()系統調用的第一個參數為1。

在socketcall()系統調用中,根據第一個參數執行對應的操作。

所以下面SOCKOP_對應的socket API都是通過socketcall()實現的,然后在socketcall()里面進行處理。

#define SOCKOP_invalid        -1
#define SOCKOP_socket        1
#define SOCKOP_bind        2
#define SOCKOP_connect        3
#define SOCKOP_listen        4
#define SOCKOP_accept        5
#define SOCKOP_getsockname    6
#define SOCKOP_getpeername    7
#define SOCKOP_socketpair    8
#define SOCKOP_send        9
#define SOCKOP_recv        10
#define SOCKOP_sendto        11
#define SOCKOP_recvfrom        12
#define SOCKOP_shutdown        13
#define SOCKOP_setsockopt    14
#define SOCKOP_getsockopt    15
#define SOCKOP_sendmsg        16
#define SOCKOP_recvmsg        17
#define SOCKOP_accept4        18
#define SOCKOP_recvmmsg        19
#define SOCKOP_sendmmsg        20

#define __SOCKETCALL1(name, a1) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [1]) { (long int) (a1) }))
...
#define __SOCKETCALL6(name, a1, a2, a3, a4, a5, a6) \
  INLINE_SYSCALL (socketcall, 2, name, \
     ((long int [6]) { (long int) (a1), (long int) (a2), (long int) (a3), \
                       (long int) (a4), (long int) (a5), (long int) (a6) }))


#define SOCKETCALL(name, args...)                    \
  ({                                    \
    long int sc_ret = __SOCKETCALL (SOCKOP_##name, args);        \
    sc_ret;                                \
  })

2.2 weak_alias()

weak_alias()是一個宏,其目的是為函數添加一個“弱”別名,與“強”符號進行區分。

如果調用函數對應的函數無“強”符號對應的函數,則會調用該別名對應的函數。所謂“強”符號的函數名就是普通聲明定義的函數對應的函數名。

這里如果沒有定義connect()函數,調用connect()實際就會轉到__socket()。

# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
  extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));

2.3 libc_hidden_def()

libc_hidden_def()的定義在libc-symbols.h中。

# define libc_hidden_def(name) hidden_def (name) 

3. socketcall及socket系統調用分析

可以說socketcall()是所有socket調用的入口,socketcall()根據call的值switch-case到對應的函數中。這些函數和單獨系統調用基本一致。

下面就先來分析一下socketcall()函數。

#define SYS_SOCKET    1        /* sys_socket(2)        */
#define SYS_BIND    2        /* sys_bind(2)            */
#define SYS_CONNECT    3        /* sys_connect(2)        */
...
#define SYS_SENDMMSG    20        /* sys_sendmmsg(2)        */

/* Argument list sizes for compat_sys_socketcall */
#define AL(x) ((x) * sizeof(u32))
static unsigned char nas[21] = {----------------------------------------nas[]將call id作為下標,得到對應系統調用參數的總大小。這里是上面call id和系統調用的一座橋梁。
    AL(0), AL(3), AL(3), AL(3), AL(2), AL(3),
    AL(3), AL(3), AL(4), AL(4), AL(4), AL(6),
    AL(6), AL(2), AL(5), AL(5), AL(3), AL(3),
    AL(4), AL(5), AL(4)
};

COMPAT_SYSCALL_DEFINE2(socketcall, int, call, u32 __user *, args)
{
    u32 a[AUDITSC_ARGS];
    unsigned int len;
    u32 a0, a1;
    int ret;

    if (call < SYS_SOCKET || call > SYS_SENDMMSG)-----------------------判斷call范圍,從SYS_SOCKET到SYS_SOCKET。 return -EINVAL;
    len = nas[call];----------------------------------------------------根據call id獲取args大小。 if (len > sizeof(a))
        return -EINVAL;

    if (copy_from_user(a, args, len))
        return -EFAULT;

    ret = audit_socketcall_compat(len / sizeof(a[0]), a);---------------未定義CONFIG_AUDITSYSCALL直接返回0。 if (ret)
        return ret;

    a0 = a[0];
    a1 = a[1];

    switch (call) {
    case SYS_SOCKET:
        ret = sys_socket(a0, a1, a[2]);
        break;
    case SYS_BIND:
        ret = sys_bind(a0, compat_ptr(a1), a[2]);
        break;
    case SYS_CONNECT:
        ret = sys_connect(a0, compat_ptr(a1), a[2]);
        break;
...
    default:
        ret = -EINVAL;
        break;
    }
    return ret;
}

結合socketcall()系統調用的實現和API單獨系統調用,可以看出兩者的實現是一致的。

3.1 sys_socket()

struct socket在內核中表示一個socket,struct sock在網絡層表示一個socket。

struct socket {
    socket_state        state;-----------------------------表示當前socket的狀態。

    kmemcheck_bitfield_begin(type);
    short            type;---------------------------------對應enum socket_type,等於sys_socket()傳入的type參數。
    kmemcheck_bitfield_end(type);

    unsigned long        flags;

    struct socket_wq __rcu    *wq;

    struct file        *file;
    struct sock        *sk;
    const struct proto_ops    *ops;------------------------是family和type兩者綜合的操作函數集。
};

sys_socket()創建一個struct socket,根據family從net_families[]找到對應協議族;然后在從type找到具體使用哪種類型struct proto_ops。

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    int retval;
    struct socket *sock;
    int flags;
...
    flags = type & ~SOCK_TYPE_MASK;
    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;
    type &= SOCK_TYPE_MASK;---------------------------------------------通過SOCK_TYPE_MASK將傳入的type分開,一部分是flags,另一部分是0~15之間的socket type。 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    retval = sock_create(family, type, protocol, &sock);----------------根據family/type/protocol創建一個struct socket。 if (retval < 0)
        goto out;

    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));-------為新創建的struct socket分配一個文件描述符。 if (retval < 0)
        goto out_release;

out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

int sock_create(int family, int type, int protocol, struct socket **res)
{
    return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

int __sock_create(struct net *net, int family, int type, int protocol,
             struct socket **res, int kern)
{
    int err;
    struct socket *sock;
    const struct net_proto_family *pf;
...
    if (family == PF_INET && type == SOCK_PACKET) {
        pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
                 current->comm);
        family = PF_PACKET;
    }

    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;

    sock = sock_alloc();--------------------------------------------------創建一個struct inode和struct socket,並將兩者綁定起來。 if (!sock) {
        net_warn_ratelimited("socket: no more sockets\n");
        return -ENFILE;    /* Not exactly a match, but its the
                   closest posix thing */
    }

    sock->type = type;----------------------------------------------------設置socket類型。

#ifdef CONFIG_MODULES
    if (rcu_access_pointer(net_families[family]) == NULL)
        request_module("net-pf-%d", family);
#endif

    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);---------------------------net_families使用下標對應AF_XXX,通過net_families[family]可以獲得對應struct net_proto_family。
    err = -EAFNOSUPPORT;
    if (!pf)
        goto out_release;

    if (!try_module_get(pf->owner))
        goto out_release;

    rcu_read_unlock();

    err = pf->create(net, sock, protocol, kern);--------------------------調用具體AF_XX對應的create成員,比如AF_UNIX對應unix_create()if (err < 0)
        goto out_module_put;

    if (!try_module_get(sock->ops->owner))
        goto out_module_busy;

    module_put(pf->owner);
    err = security_socket_post_create(sock, family, type, protocol, kern);
    if (err)
        goto out_sock_release;
    *res = sock;

    return 0;
...
}

net_families[]保存了所有AF_XXX對應的struct net_proto_family。

這些struct net_proto_family通過sock_register()注冊,通過sock_unregister()去注冊。

static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

int sock_register(const struct net_proto_family *ops)
{
    int err;
...
    spin_lock(&net_family_lock);
    if (rcu_dereference_protected(net_families[ops->family],
                      lockdep_is_held(&net_family_lock)))
        err = -EEXIST;
    else {
        rcu_assign_pointer(net_families[ops->family], ops);---------------主要就是講struct net_proto_family賦給net_families[]。
        err = 0;
    }
    spin_unlock(&net_family_lock);
return err;
}

void sock_unregister(int family)
{
    BUG_ON(family < 0 || family >= NPROTO);

    spin_lock(&net_family_lock);
    RCU_INIT_POINTER(net_families[family], NULL);
    spin_unlock(&net_family_lock);

    synchronize_rcu();
}

下面以AF_UNIX為例,看看不同type的處理。

static int __init af_unix_init(void)
{
...
 sock_register(&unix_family_ops);
...
}

static const struct net_proto_family unix_family_ops = {
    .family = PF_UNIX,
    .create = unix_create,
    .owner    = THIS_MODULE,
};

static int unix_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{
    if (protocol && protocol != PF_UNIX)
        return -EPROTONOSUPPORT;

    sock->state = SS_UNCONNECTED;

    switch (sock->type) {-------------------------------------------------可以看出AF_UNIX僅支持SOCK_STREAM、SOCK_STREAM、SOCK_STREAM、SOCK_STREAM幾種形式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;
    case SOCK_SEQPACKET:
        sock->ops = &unix_seqpacket_ops;
        break;
    default:
        return -ESOCKTNOSUPPORT;
    }
    return unix_create1(net, sock, kern) ? 0 : -ENOMEM;--------------------創建並初始化struct sock。
}

static const struct proto_ops unix_stream_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

static const struct proto_ops unix_dgram_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

static const struct proto_ops unix_seqpacket_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
...
    .set_peek_off =    unix_set_peek_off,
};

unix_create1()函數分配並且初始化struct sock,然后作為struct socket的成員sk。

static struct sock *unix_create1(struct net *net, struct socket *sock, int kern)
{
    struct sock *sk = NULL;
    struct unix_sock *u;

    atomic_long_inc(&unix_nr_socks);
    if (atomic_long_read(&unix_nr_socks) > 2 * get_max_files())
        goto out;

    sk = sk_alloc(net, PF_UNIX, GFP_KERNEL, &unix_proto, kern);
    if (!sk)
        goto out;

    sock_init_data(sock, sk);
    lockdep_set_class(&sk->sk_receive_queue.lock,
                &af_unix_sk_receive_queue_lock_key);

    sk->sk_allocation    = GFP_KERNEL_ACCOUNT;
    sk->sk_write_space    = unix_write_space;
    sk->sk_max_ack_backlog    = net->unx.sysctl_max_dgram_qlen;
    sk->sk_destruct        = unix_sock_destructor;
    u      = unix_sk(sk);
    u->path.dentry = NULL;
    u->path.mnt = NULL;
    spin_lock_init(&u->lock);
    atomic_long_set(&u->inflight, 0);
    INIT_LIST_HEAD(&u->link);
    mutex_init(&u->iolock); /* single task reading lock */
    mutex_init(&u->bindlock); /* single task binding lock */
    init_waitqueue_head(&u->peer_wait);
    init_waitqueue_func_entry(&u->peer_wake, unix_dgram_peer_wake_relay);
    unix_insert_socket(unix_sockets_unbound(sk), sk);
out:
    if (sk == NULL)
        atomic_long_dec(&unix_nr_socks);
    else {
        local_bh_disable();
        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
        local_bh_enable();
    }
    return sk;
}

綜上所述,sys_socket() 主要完善內核中struct socket結構體,尤其是struct proto_ops結構體。然后返回對應文件描述符給用戶空間。

后續關於socket的API都是通過文件描述符找到內核中對應的struct socket,然后調用struct proto_ops中成員來完成工作。 

3.2 sys_bind()

sys_bind()通過入參fd找到內核中表示socket對應的struct socket。

然后調用struct socket->ops->bind()進行umyaddr和fd綁定。在AF_UNIX和SOCK_STREAM情況下,調用unix_bind()。

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);-------------------------根據fd找到對應的struct socket結構體。以fs為索引從當前進程的文件描述符表files_struct中找到對應的file實例,然后從file實例中的private_data成員中獲取socket實例。 if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address);
        if (err >= 0) {
            err = security_socket_bind(sock,
                           (struct sockaddr *)&address,
                           addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                              (struct sockaddr *)
                              &address, addrlen);-------------------------------調用struct socket->ops->bind()完成地址與socket的綁定,進而和fd綁定。
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
    struct fd f = fdget(fd);
    struct socket *sock;

    *err = -EBADF;
    if (f.file) {
        sock = sock_from_file(f.file, err);
        if (likely(sock)) {
            *fput_needed = f.flags;
            return sock;
        }
        fdput(f);
    }
    return NULL;
}

3.3 sys_listen()

類似sys_bind(),sys_listen()也是同樣的通過fd找到struct socket,然后調用struct socket->ops->listen()完成主要工作。

在AF_UNIX和SOCK_STREAM情況下,調用unix_listen()。

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned int)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}

3.4 sys_connect()

服務器端socket使用bind()來綁定IP和端口,客戶端使用connect()讓系統自動選擇IP和端口。

核心也是調用struct socket->ops->connect()。

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
        int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);----------------------------------通過文件描述符fd找到對應的socket實例。 if (!sock)
        goto out;
    err = move_addr_to_kernel(uservaddr, addrlen, &address);
    if (err < 0)
        goto out_put;

    err =
        security_socket_connect(sock, (struct sockaddr *)&address, addrlen);--------------將socket地址從用戶空間拷貝到內核。 if (err)
        goto out_put;

    err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,-----------------調用connect()成員函數,對於AF_UNIX和SOCK_STREAM即調用unix_stream_connect()。
                 sock->file->f_flags);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

3.5 sys_accept()/sys_accept4()

sys_accept()作為accept()在內核中的實現,返回一個新的句柄,建立新的操作上下文。

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen, int, flags)
{
    struct socket *sock, *newsock;
    struct file *newfile;
    int err, len, newfd, fput_needed;
    struct sockaddr_storage address;

    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))--------------------------------------不允許使用這兩個flags。 return -EINVAL;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);--------------------------------根據fd找到struct socket。 if (!sock)
        goto out;

    err = -ENFILE;
    newsock = sock_alloc();------------------------------------------------------------創建一個新的struct socket。 if (!newsock)
        goto out_put;

    newsock->type = sock->type;--------------------------------------------------------新的socket類型和socket層操作。
    newsock->ops = sock->ops;

    /*
     * We don't need try_module_get here, as the listening socket (sock)
     * has the protocol module (sock->ops->owner) held.
     */
    __module_get(newsock->ops->owner);

    newfd = get_unused_fd_flags(flags);-------------------------------------------------分配一個空閑的文件句柄。 if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);---------為新創建的struct socket分配一個文件描述符。 if (IS_ERR(newfile)) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        sock_release(newsock);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock);
    if (err)
        goto out_fd;

    err = sock->ops->accept(sock, newsock, sock->file->f_flags);-------------------------對於AF_UNIX和SOCK_STREAM則是調用unix_accept()。 if (err < 0)
        goto out_fd;

    if (upeer_sockaddr) {----------------------------------------------------------------如果accept需要返回對端socket地址,調用newsock->ops->getname()獲取struct sockaddr並返還給用戶空間upeer_sockaddr。 if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
                      &len, 2) < 0) {
            err = -ECONNABORTED;
            goto out_fd;
        }
        err = move_addr_to_user(&address,
                    len, upeer_sockaddr, upeer_addrlen);
        if (err < 0)
            goto out_fd;
    }

    /* File flags are not inherited via accept() unlike another OSes. */

    fd_install(newfd, newfile);---------------------------------------------------------以newfd為索引,把newfile加入當前進程的文件描述符標files_struct中。
    err = newfd;

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen)
{
    return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

所以sys_accept ()主要作用就是:創建新的socket和inode並初始化完成;調用原socket->ops->accept();保存新創建socket的地址到用戶空間。

3.6 其他API

sys_getsockname()/sys_getpeername()調用相應proto_ops->getname()。

sys_send()/sys_sendto()/sys_sendmsg()/sys_sendmmsg()最終都是通過___sys_sendmsg()實現。

sys_recv()/sys_recvfrom()/sys_recvmsg()/sys_recvmmsg()最終都是通過___sys_recvmsg實現。

sys_setsockopt()/sys_getsockopt()分別調用proto_ops->setsockopt()和proto_ops->getsockopt()。

sys_socketpair()調用proto_ops->socketpair(),sys_shutdown()調用proto_ops->shutdown()。

4. 簡單基於Socket的Server/Client通信

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM