Linux TCP/IP协议栈之Socket的实现分析 第一部份 Socket套接字的创建 (-)

内核版本:2.6.12
作者:kendo
版权所有,转载请注明出处[www.skynet.org.cn];
说明:这仅仅是一个笔记,由于偶的水平有限,我甚至不能保证其中内容正确率超过80%。另外,我不太习惯在代码中注解来自哪个文件,第几行之类的,因为偶是直接通过source insight双击鼠标跳转之。

第一部份 Socket套接字的创建

socket并不是TCP/IP协议的一部份。
从广义上来讲,socket是Unix/Linux抽像的进程间通讯的一种方法。网络socket通讯仅仅是其若干协议中的一类。而tcp/ip又是网络这类中的一种。
从tcp/ip的解度看socket,它更多地体现了用户API与协议栈的一个中间层接口层。用户通过调用socket API将报文递交给协议栈,或者从协议栈中接收报文件。

一、系统总入口

Linux内核为所有的与socket有关的操作的API,提供了一个统一的系统调用入口,其代码在net/socket.c中:

asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
        unsigned long a[6];
        unsigned long a0,a1;
        int err;

        if(call<1||call>SYS_RECVMSG)
                return -EINVAL;

        /* copy_from_user should be SMP safe. */
        if (copy_from_user(a, args, nargs[call]))
                return -EFAULT;
                
        a0=a[0];
        a1=a[1];
        
        switch(call) 
        {
                case SYS_SOCKET:
                        err = sys_socket(a0,a1,a[2]);
                        break;
                case SYS_BIND:
                        err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
                        break;
                case SYS_CONNECT:
                        err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
                        break;
                case SYS_LISTEN:
                        err = sys_listen(a0,a1);
                        break;
                case SYS_ACCEPT:
                        err = sys_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
                        break;
                case SYS_GETSOCKNAME:
                        err = sys_getsockname(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
                        break;
                case SYS_GETPEERNAME:
                        err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
                        break;
                case SYS_SOCKETPAIR:
                        err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
                        break;
                case SYS_SEND:
                        err = sys_send(a0, (void __user *)a1, a[2], a[3]);
                        break;
                case SYS_SENDTO:
                        err = sys_sendto(a0,(void __user *)a1, a[2], a[3],
                                         (struct sockaddr __user *)a[4], a[5]);
                        break;
                case SYS_RECV:
                        err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
                        break;
                case SYS_RECVFROM:
                        err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
                                           (struct sockaddr __user *)a[4], (int __user *)a[5]);
                        break;
                case SYS_SHUTDOWN:
                        err = sys_shutdown(a0,a1);
                        break;
                case SYS_SETSOCKOPT:
                        err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
                        break;
                case SYS_GETSOCKOPT:
                        err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
                        break;
                case SYS_SENDMSG:
                        err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
                        break;
                case SYS_RECVMSG:
                        err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
                        break;
                default:
                        err = -EINVAL;
                        break;
        }
        return err;
}

首先调用copy_from_user将用户态参数拷贝至数组a。但是问题在于,每个被调用的API的参数不尽相同,那么每次拷贝的字节在小如果断定?
来看其第三个参数nargs[call],其中call是操作码,后面有个大大的switch...case就是判断它。对应的操作码定义在include/linux/net.h:

#define SYS_SOCKET        1                /* sys_socket(2)                */
#define SYS_BIND        2                /* sys_bind(2)                        */
#define SYS_CONNECT        3                /* sys_connect(2)                */
#define SYS_LISTEN        4                /* sys_listen(2)                */
#define SYS_ACCEPT        5                /* sys_accept(2)                */
#define SYS_GETSOCKNAME        6                /* sys_getsockname(2)                */
#define SYS_GETPEERNAME        7                /* sys_getpeername(2)                */
#define SYS_SOCKETPAIR        8                /* sys_socketpair(2)                */
#define SYS_SEND        9                /* sys_send(2)                        */
#define SYS_RECV        10                /* sys_recv(2)                        */
#define SYS_SENDTO        11                /* sys_sendto(2)                */
#define SYS_RECVFROM        12                /* sys_recvfrom(2)                */
#define SYS_SHUTDOWN        13                /* sys_shutdown(2)                */
#define SYS_SETSOCKOPT        14                /* sys_setsockopt(2)                */
#define SYS_GETSOCKOPT        15                /* sys_getsockopt(2)                */
#define SYS_SENDMSG        16                /* sys_sendmsg(2)                */
#define SYS_RECVMSG        17                /* sys_recvmsg(2)                */

而数组nargs则根据操作码的不同,计算对应的参数的空间大小:

/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={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)};
#undef AL

当拷贝完成参数后,就进入一个switch...case...判断操作码,跳转至对应的系统接口。

二、 sys_socket函数

操作码SYS_SOCKET是由sys_socket()实现的:

asmlinkage long sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;

        retval = sock_create(family, type, protocol, &sock);
        if (retval < 0)
                goto out;

        retval = sock_map_fd(sock);
        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;
}

在分析这段代码之间,首先来看,创建一个Socket,对内核而言,究竟意味着什么?究竟需要内核干什么事?

当用户空间要创建一个socke接口时,会调用API函数:

int socket(int domain, int type, int protocol);

函数,其三个参数分别表示协议族、协议类型(面向连接或无连接)以及协议。

对于用户态而言,一个Scoket,就是一个特殊的,已经打开的文件。为了对socket抽像出文件的概念,内核中为socket定义了一个专门的文件系统类型sockfs:
static struct vfsmount *sock_mnt;

static struct file_system_type sock_fs_type = {
        .name =                "sockfs",
        .get_sb =        sockfs_get_sb,
        .kill_sb =        kill_anon_super,
};

在模块初始化的时候,安装该文件系统:

void __init sock_init(void)
{
        ……
        register_filesystem(&sock_fs_type);
        sock_mnt = kern_mount(&sock_fs_type);        
}

稍后还要回来继续分析安装中的一点细节。

有了文件系统后,对内核而言,创建一个socket,就是在sockfs文件系统中创建一个文件节点(inode),并建立起为了实现socket功能所 需的一整套数据结构,包括struct inode和struct socket结构。struct socket结构在内核中,就代表了一个"Socket",当一个struct socket数据结构被分配空间后,再将其与一个已打开的文件“建立映射关系”。这样,用户态就可以用抽像的文件的概念来操作socket了——当然,由 于网络的特殊性,至少就目前而言,这种抽像,并不如其它模块的抽像那么完美。

这里socket的实现,和文件系统密切相关。这里就不再分析Linux的文件系统了,这里只分配与socket相关的一些细节,其它的都一一跳过,呵呵,希望也能有水平再写一篇《Linux文件系统的设计与实现简析》。

文件系统struct vfsmount中有一个成员指针mnt_sb指向该文件系统的超级块,而超级块结构struct super_lock有一个重要的成员s_op指向了超级块的操作函数表,其中有函数指针alloc_inode()即为在给定的超级块下创建并初始化一 个新的索引节点对像。也就是调用:

sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);

当然,连同相关的处理细节一起,这一操作被层层封装至一个上层函数 new_inode()。

那如何分配一个struct socket结构呢?如前所述,一个socket总是与一个inode密切相关的。当然,在inode中,设置一个socket成员,是完全可行的,但是 这貌似浪费了空间——毕竟,更多的文件系统没有socket这个东东。所以,内核引入了另一个socket_alloc结构:

struct socket_alloc {
        struct socket socket;
        struct inode vfs_inode;
};

显而易见,该结构实现了inode和socket的封装。已经一个inode,可以通过宏SOCKET_I来获取与之对应的socket:
sock = SOCKET_I(inode);

static inline struct socket *SOCKET_I(struct inode *inode)
{
        return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}

但是,这样做,也同时意味着,在分配一个inode后,必须再分配一个socket_alloc结构,并实现对应的封装。否则,container_of又能到哪儿去找到socket呢?现在来简要地看一个这个流程——这是文件系统安装中的一个重要步骤:

struct vfsmount *kern_mount(struct file_system_type *type)
{
        return do_kern_mount(type->name, 0, type->name, NULL);
}



struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
        struct file_system_type *type = get_fs_type(fstype);
        struct super_block *sb = ERR_PTR(-ENOMEM);
                ……
                sb = type->get_sb(type, flags, name, data);
                ……
               mnt->mnt_sb = sb;
               ……
}

do_kern_mount函数中,先根据注册的文件系统类型,调用get_fs_type获取之,也就是我们之前注册的sock_fs_type,然后调用它的get_sb成员函数指针,获取相应的超级块sb。最后,调置文件系统的超级块成员指针,使之指向对应的值。
这里get_sb函数指针,指向之前初始化的sockfs_get_sb()函数。
static struct super_block *sockfs_get_sb(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
{
        return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC);
}

注意其第三个参数sockfs_ops,它封装了sockfs的功能函数表:

static struct super_operations sockfs_ops = {
        .alloc_inode =        sock_alloc_inode,
        .destroy_inode =sock_destroy_inode,
        .statfs =        simple_statfs,
};



struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,
        struct super_operations *ops, unsigned long magic)
{
        struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
                ……
        
                s->s_op = ops ? ops : &default_ops;
}

这里就是先获取/分配一个超级块,然后初始化超级块的各成员,包括s_op,我们前面提到过它,它封装了对应的功能函数表。这里s_op自然就指向了sockfs_ops。那前面提到的new_inode()函数分配inode时调用的:

sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);

这个alloc_inode函数指针也就是sockfs_ops的sock_alloc_inode()函数——转了一大圈,终于指到它了。
来看看sock_alloc_inode是如何分配一个inode节点的:

static struct inode *sock_alloc_inode(struct super_block *sb)
{
        struct socket_alloc *ei;
        ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL);
        if (!ei)
                return NULL;
        init_waitqueue_head(&ei->socket.wait);
        
        ei->socket.fasync_list = NULL;
        ei->socket.state = SS_UNCONNECTED;
        ei->socket.flags = 0;
        ei->socket.ops = NULL;
        ei->socket.sk = NULL;
        ei->socket.file = NULL;
        ei->socket.flags = 0;

        return &ei->vfs_inode;
}

函数先分配了一个用于封装socket和inode的ei,然后在高速缓存中为之申请了一块空间。这样,inode和socket就同时都被分配了。接下来初始化socket的各个成员,这些成员,在后面都会一一提到。

/**
*  struct socket - general BSD socket
*  @state: socket state (%SS_CONNECTED, etc)
*  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
*  @ops: protocol specific socket operations
*  @fasync_list: Asynchronous wake up list
*  @file: File back pointer for gc
*  @sk: internal networking protocol agnostic socket representation
*  @wait: wait queue for several uses
*  @type: socket type (%SOCK_STREAM, etc)
*/
struct socket {
        socket_state                state;
        unsigned long                flags;
        struct proto_ops        *ops;
        struct fasync_struct        *fasync_list;
        struct file                *file;
        struct sock                *sk;
        wait_queue_head_t        wait;
        short                        type;
};

OK,至目前为止,分配inode、socket以及两者如何关联,都已一一分析了。
最后一个关键问题,就是如何把socket与一个已打开的文件,建立映射关系。

在内核中,用struct file结构描述一个已经打开的文件,指向该结构的指针内核中通常用file或filp来描述。我们知道,内核中,可以通过全局项current来获得当 前进程,它是一个struct task_struct类型的指针。tastk_struct有一个成员:
struct files_struct *files;
指向一个已打开的文件。当然,由于一个进程可能打开多个文件,所以,struct files_struct结构有
struct file * fd_array[NR_OPEN_DEFAULT];
成员,这是个数组,以文件描述符为下标,即current->files->fd[fd],可以找到与当前进程指定文件描述符的文件。

有了这些基础,如果要把一个socket与一个已打开的文件建立映射,首先要做的就是为socket分配一个struct file,并申请分配一个相应的文件描述符fd。因为socket并不支持open方法(前面说socket的文件界面的抽像并不完美,这应该是一个佐证 吧?),所以不能期望用户界面通过调用open() API来分配一个struct file,而是通过调用get_empty_filp来获取:

struct file *file = get_empty_filp();

同样地:

int fd;
fd = get_unused_fd();

获取一个空间的文件描述符

然后,让current的files指针的fd数组的fd索引项指向该file:

void fastcall fd_install(unsigned int fd, struct file * file)
{
        struct files_struct *files = current->files;
        spin_lock(&files->file_lock);
        if (unlikely(files->fd[fd] != NULL))
                BUG();
        files->fd[fd] = file;
        spin_unlock(&files->file_lock);
}

OK, 做到这一步,有了一个文件描述符fd和一个打开的文件file,它们与当前进程相连,但是好像与创建的socket并无任何瓜葛。要做的映射还是没有进 展。struct file或者文件描述述fd或current都没有任何能够与inode或者是socket相关的东东。这需要一个中间的桥梁,目录项:struct dentry结构。
因为一个文件都有与其对应的目录项:
struct file {
        struct list_head        f_list;
        struct dentry                *f_dentry;
……

而一个目录项:
struct dentry {
……
        struct inode *d_inode;                /* Where the name belongs to - NULL is
                                         * negative */

d_inode成员指向了与之对应的inode节点……

而之前已经创建了一个inode节点和与之对应的socket。
所以,现在要做的,就是:
“先为当前文件分配一个对应的目录项,再将已创建的inode节点安装至该目录项”
这样,一个完成的映射关系:
进程、文件描述符、打开文件、目录项、inode节点、socket就完整地串起来了。

基本要分析的一些前导的东东都一一罗列了,虽然已尽量避免陷入文件系统的细节分析,但是还是不可避免地进入其中,因为它们关系实现太紧密了。现在可以来看套接字的创建过程了:

asmlinkage long sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;

        retval = sock_create(family, type, protocol, &sock);
        if (retval < 0)
                goto out;

        retval = sock_map_fd(sock);
        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(family, type, protocol, res, 0);
}

三、af_inet协议簇的协议封装

接下来,函数调用之前已经注的inet_family_ops的函数指针create,也就是inet_create()函数,前面,可以说一个通用的 socket已经创建好了,这里要完成与协议本身相关的一些创建socket的工作。这一部份的工作比较复杂,还是先来看看af_inet.c中的模块初 始化时候,做了哪些与此相关的工作。

要引入的第一个数据结构是struct inet_protosw,它封装了一个协议类型(如SOCK_STREAM、SOCK_DGRAM等)与ip协议中对应的传输层协议:

/* This is used to register socket interfaces for IP protocols.  */
struct inet_protosw {
        struct list_head list;

        /* These two fields form the lookup key.  */
        unsigned short         type;           /* This is the 2nd argument to socket(2). */
        int                 protocol; /* This is the L4 protocol number.  */

        struct proto         *prot;
        struct proto_ops *ops;
  
        int              capability; /* Which (if any) capability do
                                      * we need to use this socket
                                      * interface?
                                      */
        char             no_check;   /* checksum on rcv/xmit/none? */
        unsigned char         flags;      /* See INET_PROTOSW_* below.  */
};
#define INET_PROTOSW_REUSE 0x01             /* Are ports automatically reusable? */
#define INET_PROTOSW_PERMANENT 0x02  /* Permanent protocols are unremovable. */

type 是协议类型,对于ipv4而言,就是SOCK_STREAM、SOCK_DGRAM或者是SOCK_RAW之一。protocol是传输层的协议号。 prot用于描述一个具体的传输层协议,而ops指向对应的当前协议类型的操作函数集。针对不同的协议类型,定义了不同的ops:

struct proto_ops inet_stream_ops = {
        .family =        PF_INET,
        .owner =        THIS_MODULE,
        .release =        inet_release,
        .bind =                inet_bind,
        .connect =        inet_stream_connect,
        .socketpair =        sock_no_socketpair,
        .accept =        inet_accept,
        .getname =        inet_getname,
        .poll =                tcp_poll,
        .ioctl =        inet_ioctl,
        .listen =        inet_listen,
        .shutdown =        inet_shutdown,
        .setsockopt =        sock_common_setsockopt,
        .getsockopt =        sock_common_getsockopt,
        .sendmsg =        inet_sendmsg,
        .recvmsg =        sock_common_recvmsg,
        .mmap =                sock_no_mmap,
        .sendpage =        tcp_sendpage
};



struct proto_ops inet_dgram_ops = {
        .family =        PF_INET,
        .owner =        THIS_MODULE,
        .release =        inet_release,
        .bind =                inet_bind,
        .connect =        inet_dgram_connect,
        .socketpair =        sock_no_socketpair,
        .accept =        sock_no_accept,
        .getname =        inet_getname,
        .poll =                udp_poll,
        .ioctl =        inet_ioctl,
        .listen =        sock_no_listen,
        .shutdown =        inet_shutdown,
        .setsockopt =        sock_common_setsockopt,
        .getsockopt =        sock_common_getsockopt,
        .sendmsg =        inet_sendmsg,
        .recvmsg =        sock_common_recvmsg,
        .mmap =                sock_no_mmap,
        .sendpage =        inet_sendpage,
};



/*
* For SOCK_RAW sockets; should be the same as inet_dgram_ops but without
* udp_poll
*/
static struct proto_ops inet_sockraw_ops = {
        .family =        PF_INET,
        .owner =        THIS_MODULE,
        .release =        inet_release,
        .bind =                inet_bind,
        .connect =        inet_dgram_connect,
        .socketpair =        sock_no_socketpair,
        .accept =        sock_no_accept,
        .getname =        inet_getname,
        .poll =                datagram_poll,
        .ioctl =        inet_ioctl,
        .listen =        sock_no_listen,
        .shutdown =        inet_shutdown,
        .setsockopt =        sock_common_setsockopt,
        .getsockopt =        sock_common_getsockopt,
        .sendmsg =        inet_sendmsg,
        .recvmsg =        sock_common_recvmsg,
        .mmap =                sock_no_mmap,
        .sendpage =        inet_sendpage,
};

从各个函数指针的名称,我们就可以大约知道它们是做什么事的了。进一步进以看到,它们的函数指针指向的函数差不多都是相同的。除了一些细节上的区别,例如后面两种协议类型并不支持listen。

socket() API第二个参数是协议类型,第三个参数是该协议类型下的协议——不过对于ipv4而言,它们都是一一对应的。但是从抽像封装的角度看,数据结构的设计本 身应该满足一个协议类型下边,可能存在多个不同的协议,即一对多的情况。而一一对应,仅是它们的特例:

/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
static struct inet_protosw inetsw_array[] =
{
        {
                .type =       SOCK_STREAM,
                .protocol =   IPPROTO_TCP,
                .prot =       &tcp_prot,
                .ops =        &inet_stream_ops,
                .capability = -1,
                .no_check =   0,
                .flags =      INET_PROTOSW_PERMANENT,
        },

        {
                .type =       SOCK_DGRAM,
                .protocol =   IPPROTO_UDP,
                .prot =       &udp_prot,
                .ops =        &inet_dgram_ops,
                .capability = -1,
                .no_check =   UDP_CSUM_DEFAULT,
                .flags =      INET_PROTOSW_PERMANENT,
       },
        

       {
               .type =       SOCK_RAW,
               .protocol =   IPPROTO_IP,        /* wild card */
               .prot =       &raw_prot,
               .ops =        &inet_sockraw_ops,
               .capability = CAP_NET_RAW,
               .no_check =   UDP_CSUM_DEFAULT,
               .flags =      INET_PROTOSW_REUSE,
       }
};

数 组的每一个元素,就是支持的一种协议名称,例如IPOROTO_TCP,但是由于IPV4本身协议类型跟协议是一一对应的,所以没有更多的.type= SOCK_xxx了。这样数组实现了对PF_INET协议族下支持的协议类型,以及协议类型下边的协议进行了封装,虽然事实上它们是一一对应的关系,不过 理论上,完全可能存在一对多的可能。

数组内,封装的一个具体的协议,由struct proto结构来描述:

/* Networking protocol blocks we attach to sockets.
* socket layer -> transport layer interface
* transport -> network interface is defined by struct inet_proto
*/
struct proto {
        void                        (*close)(struct sock *sk, 
                                        long timeout);
        int                        (*connect)(struct sock *sk,
                                        struct sockaddr *uaddr, 
                                        int addr_len);
        int                        (*disconnect)(struct sock *sk, int flags);

        struct sock *                (*accept) (struct sock *sk, int flags, int *err);

        int                        (*ioctl)(struct sock *sk, int cmd,
                                         unsigned long arg);
        int                        (*init)(struct sock *sk);
        int                        (*destroy)(struct sock *sk);
        void                        (*shutdown)(struct sock *sk, int how);
        int                        (*setsockopt)(struct sock *sk, int level, 
                                        int optname, char __user *optval,
                                        int optlen);
        int                        (*getsockopt)(struct sock *sk, int level, 
                                        int optname, char __user *optval, 
                                        int __user *option);           
        int                        (*sendmsg)(struct kiocb *iocb, struct sock *sk,
                                           struct msghdr *msg, size_t len);
        int                        (*recvmsg)(struct kiocb *iocb, struct sock *sk,
                                           struct msghdr *msg,
                                        size_t len, int noblock, int flags, 
                                        int *addr_len);
        int                        (*sendpage)(struct sock *sk, struct page *page,
                                        int offset, size_t size, int flags);
        int                        (*bind)(struct sock *sk, 
                                        struct sockaddr *uaddr, int addr_len);

        int                        (*backlog_rcv) (struct sock *sk, 
                                                struct sk_buff *skb);

        /* Keeping track of sk's, looking them up, and port selection methods. */
        void                        (*hash)(struct sock *sk);
        void                        (*unhash)(struct sock *sk);
        int                        (*get_port)(struct sock *sk, unsigned short snum);

        /* Memory pressure */
        void                        (*enter_memory_pressure)(void);
        atomic_t                *memory_allocated;        /* Current allocated memory. */
        atomic_t                *sockets_allocated;        /* Current number of sockets. */
        /*
         * Pressure flag: try to collapse.
         * Technical note: it is used by multiple contexts non atomically.
         * All the sk_stream_mem_schedule() is of this nature: accounting
         * is strict, actions are advisory and have some latency.
         */
        int                        *memory_pressure;
        int                        *sysctl_mem;
        int                        *sysctl_wmem;
        int                        *sysctl_rmem;
        int                        max_header;

        kmem_cache_t                *slab;
        unsigned int                obj_size;

        struct module                *owner;

        char                        name[32];

        struct list_head        node;

        struct {
                int inuse;
                u8  __pad[SMP_CACHE_BYTES - sizeof(int)];
        } stats[NR_CPUS];
};

以TCP协议为例,TCP协议的sokcet操作函数都被封装在这里了。

struct proto tcp_prot = {
        .name                        = "TCP",
        .owner                        = THIS_MODULE,
        .close                        = tcp_close,
        .connect                = tcp_v4_connect,
        .disconnect                = tcp_disconnect,
        .accept                        = tcp_accept,
        .ioctl                        = tcp_ioctl,
        .init                        = tcp_v4_init_sock,
        .destroy                = tcp_v4_destroy_sock,
        .shutdown                = tcp_shutdown,
        .setsockopt                = tcp_setsockopt,
        .getsockopt                = tcp_getsockopt,
        .sendmsg                = tcp_sendmsg,
        .recvmsg                = tcp_recvmsg,
        .backlog_rcv                = tcp_v4_do_rcv,
        .hash                        = tcp_v4_hash,
        .unhash                        = tcp_unhash,
        .get_port                = tcp_v4_get_port,
        .enter_memory_pressure        = tcp_enter_memory_pressure,
        .sockets_allocated        = &tcp_sockets_allocated,
        .memory_allocated        = &tcp_memory_allocated,
        .memory_pressure        = &tcp_memory_pressure,
        .sysctl_mem                = sysctl_tcp_mem,
        .sysctl_wmem                = sysctl_tcp_wmem,
        .sysctl_rmem                = sysctl_tcp_rmem,
        .max_header                = MAX_TCP_HEADER,
        .obj_size                = sizeof(struct tcp_sock),
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值