首页 > 系统相关 >Linux内核accept系统调用源码分析

Linux内核accept系统调用源码分析

时间:2024-01-21 10:45:34浏览次数:32  
标签:socket err sock accept 源码 Linux inet struct

内核版本:Linux 3.10

内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)

一、应用层-accept()函数

/**
 * sockfd:监听socket的文件描述符
 * addr:存放地址信息的结构体的首地址(用来保存客户端的IP、Port)
 * addrlen:存放地址信息的结构体的大小
 * 成功,返回一个通信socket的文件描述符;失败返回-1
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

通过网络栈专用操作函数集的总入口函数(sys_socketcall函数),请求会分发到sys_accept4()函数。
具体细节可以参考《Linux内核socket系统调用源码分析》

二、内核实现

1.sys_accept4()函数

建立服务器与客户端通信的"桥梁"。
为客户端创建一个socket(后续我们称它为通信socket);
并在网络文件系统中为它申请文件号和文件结构;
最终返回通信socket的文件描述符和客户端地址信息。

// file: net/socket.c
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;

    ......

    // 获取fd对应的socket结构
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    err = -ENFILE;
    newsock = sock_alloc(); //分配一个新的socket结构体
    if (!newsock)
        goto out_put;

    newsock->type = sock->type; //继承监听socket结构的类型和函数表
    newsock->ops = sock->ops;

    __module_get(newsock->ops->owner); //目前是空函数

    newfd = get_unused_fd_flags(flags); //获取一个未使用的文件描述符,这个描述符也就是accept()返回的fd
    if (unlikely(newfd < 0)) {
        err = newfd;
        sock_release(newsock);
        goto out_put;
    }
    
    // 创建一个file结构体,同时将这个file结构体和刚刚创建的newsock关联
    newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    if (unlikely(IS_ERR(newfile))) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        sock_release(newsock);
        goto out_put;
    }

    err = security_socket_accept(sock, newsock); //SElinux相关,忽略
    if (err)
        goto out_fd;

    // 调用监听socket函数表的接收连接函数
    err = sock->ops->accept(sock, newsock, sock->file->f_flags);
    if (err < 0)
        goto out_fd;

    if (upeer_sockaddr) { //如果要获取对端连接信息,那么拷贝对应信息到用户空间
        // 调用inet_getname()获取对端信息
        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;
    }

    fd_install(newfd, newfile); //将文件描述符fd和文件结构体file关联到一起
    err = newfd; //返回新分配的文件描述符(通信socket文件描述符)

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

在服务器socket创建过程中,sock->ops = answer->ops,而answer对应的结构:

// file: net/ipv4/af_inet.c
static struct inet_protosw inetsw_array[] =
{
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .no_check =   0,
        .flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
    },
    ......
}

所以,sock->ops函数操作表,挂入了inet_stream_ops。具体细节可参考《Linux内核socket系统调用源码分析

// file: net/ipv4/af_inet.c
const struct proto_ops inet_stream_ops = {
    ......
    .accept           = inet_accept,
    ......
};

因此,sock->ops->accept()最终调用的是inet_accept()函数。

2.inet_accept()函数

从全连接队列中,获取一个包含客户端信息的sock,并与新创建的socket关联上。

// file: net/ipv4/af_inet.c
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
    struct sock *sk1 = sock->sk; //取得监听socket的sock指针
    int err = -EINVAL;
    // 调用对应协议的accept函数
    // 从全连接队列中获取第三次握手时创建的sock
    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);

    if (!sk2)
        goto do_err;

    lock_sock(sk2);

    sock_rps_record_flow(sk2);
    WARN_ON(!((1 << sk2->sk_state) &
          (TCPF_ESTABLISHED | TCPF_SYN_RECV |
          TCPF_CLOSE_WAIT | TCPF_CLOSE)));

    sock_graft(sk2, newsock); //将新分配的socket和接收到的sock相互关联,形成完整的通信socket

    newsock->state = SS_CONNECTED; //返回的新socket状态设置为已连接(socket、sock这是两个不同结构的状态,我们平时说的TCP状态是sock结构的状态)
    err = 0;
    release_sock(sk2);
do_err:
    return err;
}

在服务器socket创建过程中,sk1->sk_prot挂入的是tcp_prot结构:

// file: net/ipv4/tcp_ipv4.c
struct proto tcp_prot = {
    ......
    .accept            = inet_csk_accept,
    ......
};

因此,sk1->sk_prot->accept()最终调用的是inet_csk_accept()函数。

3.inet_csk_accept()函数

从全连接队列中获取一个通信sock

// file: net/ipv4/inet_connection_sock.c
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    struct sock *newsk;
    struct request_sock *req;
    int error;

    lock_sock(sk);
    error = -EINVAL;
    if (sk->sk_state != TCP_LISTEN) //接受连接之前必须处于监听状态
        goto out_err;

    // 检查全连接队列
    if (reqsk_queue_empty(queue)) { //全连接队列为空
        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); //确定睡眠时间

        error = -EAGAIN;
        if (!timeo) //非阻塞的socket,无需睡眠
            goto out_err;

        //进入循环睡眠等待连接到来
        //与第三次握手,建立连接完成时,唤醒服务器程序接收连接,对接上了
        error = inet_csk_wait_for_connect(sk, timeo); //跟客户端第一次握手等待连接类似
        if (error)
            goto out_err;
    }
    req = reqsk_queue_remove(queue); //从全连接队列中获取一个通信sock
    newsk = req->sk;

    sk_acceptq_removed(sk); //更新全连接队列计数器
    ......
}

在inet_csk_wait_for_connect()函数中,如果此时尚未有客户端发起连接,那就睡眠直到有请求到来,或者如果用户设置了超时时间,也会超时返回。另外,也有可能被信号打断。

三、总结

accept的主要工作就是从全连接队列中获取一个连接,创建通信socket,供用户使用。

标签:socket,err,sock,accept,源码,Linux,inet,struct
From: https://www.cnblogs.com/573583868wuy/p/17977591

相关文章

  • 16-Linux进程管理
    进程的概念:进程是正在执行的一个程序或命令,每一个进程都是一个运行的实体,都有自己的地址空间,并占用一定的系统资源。命令ps:查看当前系统进程状态语法:ps【选项】选项:小技巧:如果想查看进程的CPU占用率和内存占用率,可以使用aux;如果想查看进程的父进程ID可以使用ef案......
  • 17-Linux系统定时任务
    crontab服务管理注意点使用前先确认crontab的守护进程crond是否是打开的状态,一般是开机自启的。[root@192mnt]#systemctlstatuscrond#查看crond进程是否开启。当前是开启的●crond.service-CommandSchedulerLoaded:loaded(/usr/lib/systemd/system/crond.ser......
  • 18-Linux软件包管理
    RPM介绍RPM(RedHatPackageManager),RedHat软件包管理工具,类似windows里面的setup.exe是Linux这系列操作系统里面的打包安装工具,它虽然是RedHat的标志,但理念是通用的。RPM包的名称格式:Apache-1.3.23-11.i386.rpm。其中:“apache”软件名称“1.3.23-11”软件的版本号,主版本和此......
  • 19-Linux克隆虚拟机
    从现有虚拟机(关机状态)克隆出新虚拟机,右键选择管理=>克隆  点击下一步  选择虚拟机中的当前状态  选择创建完整克隆  设置虚拟机名称及存储位置等待克隆完成 ......
  • Linux常用命令
    性能监控(cpu内存磁盘网络)性能监控命令 uptime:显示系统平均负载以及系统启动时间查看CPU mpstat查看内存 vmstat15每秒刷新一次刷5次查看磁盘 ioiostat-x15查看网络 iftop查看进程资源占用 ......
  • 11-Linux用户组管理相关
    cat/etc/group:查看创建了哪些组[root@192home]#cat/etc/group...atguigu:x:1000:atguiguxiaoming:x:1001:#之前添加的没有指定组的用户,默认都创建了一个组,这个组里只有这一个用户groupadd:添加一个用户组 [root@192home]#groupaddtester#创建一个tester组[......
  • 10-Linux用户管理相关
    useradd:添加新用户[root@192~]#useraddzhangsan#添加一个新用户[root@192home]#ll/home#home目录下增加了zhangsan的用户目录,表示已经创建成功总用量0drwx------.5atguiguatguigu1281月92024atguigudrwx------.3zhangsanzhangsan781月100......
  • Linux---磁盘管理
    1.磁盘外部结构存储数据的地方磁盘分类:第一类:机械磁盘通过机械运动读写数据台式机3.5英寸第二类:固态磁盘芯片第三类:NVME磁盘PCI-E类似显卡接口类型及转速:决定了读写速度IDE接口淘汰SCSI接口不常用SATA接口笔记本台式机7.2K转/10KSAS接口服务器标......
  • Linux中关于磁盘的一些常见问题小记
    1.程序导致内存不够用程序导致内存不够用如果内存满则系统会自动杀死占用内存最高的进程来保护系统正常运行什么原因导致内存满:1.大量用户访问服务器(正常情况)需要我们添加内存2.由于程序导致内存满,而不是大量用户访问导致(找开发解决)3.由于网络的波动导致内存满需要......
  • 9-Linux时间日期相关
    date语法:date+%Y%Y:年份%m:月份%d:当前是哪一天%H:时%M:分%S:秒案例:[root@192~]#date"+%Y-%m-%d%H:%M:%S"2024-01-2022:32:58  date-d:显示指定的“时间字符串”表示的时间,而非当前时间案例: [root@192~]#date-d"1daysago"#1表示查看前一......