【C语言】Linux内核accept 系统调用代码

2024-02-17 01:04

本文主要是介绍【C语言】Linux内核accept 系统调用代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Linux 4.19内核accept 系统调用代码中文注释

/** 在使用accept时,我们尝试创建一个新的socket,与客户端建立连接,* 唤醒客户端,然后返回新的连接文件描述符(fd)。我们在内核空间收集* 连接方的地址,并在最后将其移到用户空间。这样做不干净是因为,我们* 打开socket后可能返回一个错误。** 1003.1g标准增加了通过recvmsg()查询连接挂起状态的能力。我们* 需要在重构accept时,也以一种干净的方式增加这个支持。*/int __sys_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;// 校验传入的flags标志if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))return -EINVAL;// 将SOCK_NONBLOCK转换为对应的O_NONBLOCK标志if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;// 通过文件描述符找到对应的socket,fd为文件描述符sock = sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)goto out;// ENFILE表示文件表溢出err = -ENFILE;newsock = sock_alloc(); // 分配一个新的socketif (!newsock)goto out_put;// 设置新socket的类型和操作函数newsock->type = sock->type;newsock->ops = sock->ops;/** 我们不需要调用try_module_get,因为监听socket(sock)* 已经有了协议模块(sock->ops->owner)。*/__module_get(newsock->ops->owner);// 获得未使用的文件描述符newfd = get_unused_fd_flags(flags);if (unlikely(newfd < 0)) {err = newfd;sock_release(newsock); // 释放socket资源goto out_put;}// 分配新socket文件表项newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);if (IS_ERR(newfile)) {err = PTR_ERR(newfile);put_unused_fd(newfd);goto out_put;}// 安全检查err = security_socket_accept(sock, newsock);if (err)goto out_fd;// 调用实际的accept操作err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);if (err < 0)goto out_fd;// 如果用户提供了地址存储,则获取并复制给用户if (upeer_sockaddr) {len = newsock->ops->getname(newsock,(struct sockaddr *)&address, 2);if (len < 0) {err = -ECONNABORTED;goto out_fd;}err = move_addr_to_user(&address,len, upeer_sockaddr, upeer_addrlen);if (err < 0)goto out_fd;}/* 与某些操作系统不同,文件标志不是通过accept()继承的。 */// 将新的文件描述符安装到文件表中fd_install(newfd, newfile);err = newfd;out_put:fput_light(sock->file, fput_needed); // 释放文件表项
out:return err; // 返回错误码或新的文件描述符
out_fd:fput(newfile); // 释放文件表项put_unused_fd(newfd); // 释放未使用的文件描述符goto out_put;
}// 处理accept4系统调用,将用户态参数转到内核态处理
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,int __user *, upeer_addrlen, int, flags)
{return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, flags);
}// 处理accept系统调用,flags默认为0
SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,int __user *, upeer_addrlen)
{return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

这段代码定义了两个系统调用函数:
- SYSCALL_DEFINE4(accept4, ...): 这是一个宏,它定义了`accept4`系统调用的接口。`accept4`允许用户程序在创建一个新的socket连接时指定额外的选项,比如`SOCK_CLOEXEC`和`SOCK_NONBLOCK`。它接收四个参数:文件描述符`fd`、用户空间的指向`sockaddr`结构的指针`upeer_sockaddr`、指向地址长度变量的指针`upeer_addrlen`、以及标志位`flags`。内核会将这个调用委托给`__sys_accept4`处理。
- SYSCALL_DEFINE3(accept, ...): 这个宏定义了标准的`accept`系统调用,它不接收`flags`参数(默认为0),只在`accept4`的基础上省略了最后一个参数。其余的参数和`accept4`相同。内核也会将这个调用委托给`__sys_accept4`处理,只不过`flags`参数提供了默认值0。
这两个宏最终都会导致`__sys_accept4`函数被调用来执行处理连接请求的实际工作。
- upeer_sockaddr和`upeer_addrlen`是指向用户空间的指针,表明`accept`和`accept4`在连接成功之后,可以返回新连接端点的地址信息给用户程序。
这部分的代码是Linux内核网络栈中处理接受新连接请求的关键组成部分。这些函数直接与用户空间的应用程序交互,允许它们在监听的网络端口上接受新的连接。

二、代码解释

这段代码是Linux内核4.19中处理`accept`系统调用的实现,其作用是允许服务器接受一个来自客户端的连接请求。以下是解读:
这个函数尝试创建一个新的socket,设置与客户端的链接,唤醒客户端,然后返回新创建的已连接的文件描述符(fd)。它会在内核空间收集连接方的地址信息,并将信息复制到用户空间。
这里定义了两个系统调用的接口函数:`SYSCALL_DEFINE3(accept, ...)`和`SYSCALL_DEFINE4(accept4, ...)。`accept`是基本的接受连接的系统调用,而`accept4`是一个增强版,允许用户通过额外的`flags`参数设置一些选项。
__sys_accept4函数做了以下工作:
1. 首先检查传入的`flags`参数,确保用户没有设置不合法的标志,如果是就返回`-EINVAL`(表示无效的参数)。
2. 调用`sockfd_lookup_light`函数,尝试获取与给定文件描述符`fd`关联的socket对象。如果成功,继续后续操作;否则,返回错误。
3. 分配一个新的socket结构给新的连接,并拷贝被监听socket的一些属性。
4. 获取一个未使用的文件描述符`newfd`,分配并初始化一个新的文件对象`newfile`。如果这些操作发生错误,清理并返回错误码。
5. 调用`security_socket_accept`和`sock->ops->accept`进行安全检查和实际的接受操作。这些步骤涉及底层协议的操作,以处理连接建立等细节。
6. 如果用户提供了地址存储空间(`upeer_sockaddr`),将新socket的地址信息从内核空间复制到用户空间。
7. 使用`fd_install`将新的文件对象`newfile`绑定到新的文件描述符`newfd`。
8. 进行清理工作,如果在任何一步出错,释放之前分配的资源,并返回错误码;若无误,返回新的连接文件描述符`newfd`。
该函数考虑了错误处理路径,在任何可能的错误点,都会释放占用的资源以避免内存泄露。执行成功时将返回一个代表新连接的文件描述符。
这段代码是在Linux内核中处理`accept`与`accept4`系统调用的实现,这些系统调用是由服务器程序使用的,它们允许服务器接受来自客户端的连接请求。继续分步解释这个函数的执行流程。
1. 检查传入的`flags`参数:
   - 若`flags`包含了不是`SOCK_CLOEXEC`或`SOCK_NONBLOCK`的其他标志位,则返回错误`-EINVAL`。
   - 如果`flags`指定了非阻塞标志`SOCK_NONBLOCK`,并且这个标志和系统定义的`O_NONBLOCK`不相同,则将`flags`中的`SOCK_NONBLOCK`标志替换成`O_NONBLOCK`。
2. 调用`sockfd_lookup_light`函数来查找与文件描述符`fd`对应的socket结构体。如果没有找到,设置错误号`err`,并立即跳到`out`标签处处理退出。
3. 尝试分配一个新的socket(`newsock`)给即将建立的连接。如果分配失败,设置错误号`err`为`-ENFILE`(文件表溢出),然后跳到`out_put`标签处释放已引用的socket并处理退出。
4. 尝试获取一个未使用的文件描述符`newfd`,并为这个文件描述符创建一个新的文件对象`newfile`对应于新的socket。如果获取文件描述符失败或者创建文件对象失败,设置相应的错误号,释放资源,然后退出。
5. 调用`security_socket_accept`函数进行安全检查,并调用原始socket的`accept`方法以完成连接的建立。如果这些步骤中任何一个失败了,跳到`out_fd`来处理错误。
6. 如果用户程序提供了一个地址用于存储客户端的信息(`upeer_sockaddr`),那么获取新socket的地址信息,并通过`move_addr_to_user`函数将地址信息复制到用户空间。如果这个过程中出现错误,设置错误号并跳到`out_fd`处理错误。
7. 通过`fd_install`函数将文件对象`newfile`与文件描述符`newfd`关联起来,完成文件描述符的安装。
8. 得到成功的结果`err`,这是新的文件描述符,这意味着可以通过这个描述符与客户端通信。
如果在这个过程的任何一步中出现了错误,代码会跳到错误处理的部分,执行清理工作,确保不会有资源泄露。正确执行的流程结束时,返回新打开的文件描述符`newfd`用于后续通信。
最后,定义了`SYSCALL_DEFINE4`和`SYSCALL_DEFINE3`宏,这两个宏是用来声明接受4个参数和3个参数的系统调用接口。它们确保了`accept`和`accept4`系统调用可以通过系统调用接口被用户空间的程序访问。这些宏最终将调用`__sys_accept4`来完成实际的工作。如果使用`accept`而非`accept4`,那么`flags`参数默认为0,表示标准的`accept`行为。

这篇关于【C语言】Linux内核accept 系统调用代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/716227

相关文章

R语言中的正则表达式深度解析

《R语言中的正则表达式深度解析》正则表达式即使用一个字符串来描述、匹配一系列某个语法规则的字符串,通过特定的字母、数字及特殊符号的灵活组合即可完成对任意字符串的匹配,:本文主要介绍R语言中正则表达... 目录前言一、正则表达式的基本概念二、正则表达式的特殊符号三、R语言中正则表达式的应用实例实例一:查找匹配

Go语言结构体标签(Tag)的使用小结

《Go语言结构体标签(Tag)的使用小结》结构体标签Tag是Go语言中附加在结构体字段后的元数据字符串,用于提供额外的属性信息,这些信息可以通过反射在运行时读取和解析,下面就来详细的介绍一下Tag的使... 目录什么是结构体标签?基本语法常见的标签用途1.jsON 序列化/反序列化(最常用)2.数据库操作(

利用c++判断水仙花数并输出示例代码

《利用c++判断水仙花数并输出示例代码》水仙花数是指一个三位数,其各位数字的立方和恰好等于该数本身,:本文主要介绍利用c++判断水仙花数并输出的相关资料,文中通过代码介绍的非常详细,需要的朋友可以... 以下是使用C++实现的相同逻辑代码:#include <IOStream>#include <vec

基于C++的UDP网络通信系统设计与实现详解

《基于C++的UDP网络通信系统设计与实现详解》在网络编程领域,UDP作为一种无连接的传输层协议,以其高效、低延迟的特性在实时性要求高的应用场景中占据重要地位,下面我们就来看看如何从零开始构建一个完整... 目录前言一、UDP服务器UdpServer.hpp1.1 基本框架设计1.2 初始化函数Init详解

Django调用外部Python程序的完整项目实战

《Django调用外部Python程序的完整项目实战》Django是一个强大的PythonWeb框架,它的设计理念简洁优雅,:本文主要介绍Django调用外部Python程序的完整项目实战,文中通... 目录一、为什么 Django 需要调用外部 python 程序二、三种常见的调用方式方式 1:直接 im

Java 接口定义变量的示例代码

《Java接口定义变量的示例代码》文章介绍了Java接口中的变量和方法,接口中的变量必须是publicstaticfinal的,用于定义常量,而方法默认是publicabstract的,必须由实现类... 在 Java 中,接口是一种抽象类型,用于定义类必须实现的方法。接口可以包含常量和方法,但不能包含实例

使用Redis实现会话管理的示例代码

《使用Redis实现会话管理的示例代码》文章介绍了如何使用Redis实现会话管理,包括会话的创建、读取、更新和删除操作,通过设置会话超时时间并重置,可以确保会话在用户持续活动期间不会过期,此外,展示了... 目录1. 会话管理的基本概念2. 使用Redis实现会话管理2.1 引入依赖2.2 会话管理基本操作

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

mybatis-plus分表实现案例(附示例代码)

《mybatis-plus分表实现案例(附示例代码)》MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生,:本文主要介绍my... 目录文档说明数据库水平分表思路1. 为什么要水平分表2. 核心设计要点3.基于数据库水平分表注意事项示例

Nginx服务器部署详细代码实例

《Nginx服务器部署详细代码实例》Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务,:本文主要介绍Nginx服务器部署的相关资料,文中通过代码... 目录Nginx 服务器SSL/TLS 配置动态脚本反向代理总结Nginx 服务器Nginx是一个‌高性