【C语言】SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

一、SYSCALL_DEFINE3与系统调用

在Linux操作系统中,为了从用户空间跳转到内核空间执行特定的内核级操作,使用了一种机制叫做"系统调用"(System Call)。系统调用是操作系统提供给程序员访问和使用内核功能的接口。例如,要在文件系统中创建新文件、发送网络数据或分配内存等,都需要通过系统调用来完成。
SYSCALL_DEFINE3是一个Linux内核中用来定义接收三个参数的系统调用的宏。让我们深入理解一下这个过程。

宏 SYSCALL_DEFINE3

1. 名称: SYSCALL_DEFINE3是一个合成了名称中参数数量(在这个例子中是3)的宏,表示这个宏会处理一个有三个参数的系统调用。
2. 参数:
   - socket: 这是系统调用的名字。在用户空间,例如在C语言中,我们可能使用类似`socket(…)`这样的调用。
   - int, family: 这是系统调用的第一个参数,它指定了socket的协议族。
   - int, type: 这是系统调用的第二个参数,它指定socket的类型。
   - int, protocol: 这是系统调用的第三个参数,它指定了在给定的协议族和类型下使用哪个协议。

系统调用的工作流程

1. 用户空间调用系统调用:
   程序员在用户空间程序(如C程序)中写下了一个系统调用,例如`socket(AF_INET, SOCK_STREAM, 0)`。
2. 转换为内核空间:
   当上述调用执行时,CPU切换到内核模式,这个过程通常涉及到生成一个软件中断(如x86架构的`int 0x80`指令,或者其他架构的类似机制),或者使用特定的系统调用指令(如x86-64架构的`syscall`)。
3. 系统调用分派:
   内核中有一个预先设置好的表(系统调用表),其中包括了所有系统调用对应的函数指针。执行软件中断时,会使用寄存器中的值(通常是一个系统调用号)来确定需要调用的具体函数。在x86-64架构上,这个系统调用号会被放在`RAX`寄存器中。然后系统调用表会返回对应的函数(例如`sys_socket`)。
4. 执行内核级函数:
   在这个例子中,内核会执行`__sys_socket`函数,这是内核中实现创建socket实际操作的私有函数。`__sys_scket`负责分配和设置必要的数据结构,以创建和配置一个新的socket。
5. 返回用户空间:
   一旦`__sys_socket`完成了它的工作,系统调用会返回到用户空间程序,通常携带一个值——file descriptor(文件描述符),这个值是新创建的socket的唯一标识。如系统调用执行成功,返回的是一个非负整数的文件描述符;如执行失败,就返回一个错误码(一般是-1)。

结论

通过使用宏如`SYSCALL_DEFINE3`,Linux内核的维护者可以轻松定义需要任何数量参数的系统调用,同时保持代码简洁和一致性。借助此宏能够将系统调用名称与实现细节脱钩,这样在系统调用实现或调度逻辑发生改变时,不需要对每个系统调用的定义都进行修改。

二、SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{return __sys_socket(family, type, protocol);
}

这行代码定义了一个Linux内核系统调用`socket`,其接收三个整型参数:`family`,`type`,和`protocol`。这个系统调用用来创建一个新的socket。
来逐个解释这些参数:
- family:指定了socket所使用的协议族。比如,`AF_INET`表示IPv4协议族,`AF_INET6`表示IPv6协议族,等等。
- type:指定了socket的类型,也就是通信的语义。例如,`SOCK_STREAM`表示提供顺序、可靠、双向、基于连接的字节流;`SOCK_DGRAM`表示提供数据报(一个小块的信息),它们可能会丢失或者重复,并可能不会按顺序到达。
- protocol:通常为零,让系统自动选择`family`和`type`组合的默认协议。例如,当使用`AF_INET`和`SOCK_STREAM`,默认协议是`IPPROTO_TCP`,即TCP协议。
函数名`SYSCALL_DEFINE3`是一个宏,用于定义接收三个参数的系统调用。这个宏展开后,会生成系统调用的实际实现,这里它定义了`socket`系统调用。
实现部分:`__sys_socket(family, type, protocol)`;
这是一个内核私有的函数,负责实现创建socket的逻辑。它简单地将任务委托给`__sys_socket`函数处理。这个函数会处理实际的socket创建工作,并返回一个文件描述符(file descriptor),这个文件描述符代表了新创建的socket。如果操作成功,这个文件描述符是一个非负整数;如果操作失败,则返回-1,并且设置相应的错误码。

三、int __sys_socket(int family, int type, int protocol)

int __sys_socket(int family, int type, int protocol)
{int retval;struct socket *sock;int flags;/* Check the SOCK_* constants for consistency.  */BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);flags = type & ~SOCK_TYPE_MASK;if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))return -EINVAL;type &= SOCK_TYPE_MASK;if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;retval = sock_create(family, type, protocol, &sock);if (retval < 0)return retval;return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

这段代码是一个内核函数的示例,用于创建一个新的socket。它包含了在进行网络编程时创建socket的一些基本步骤和错误检查。以下是代码的解释:
1. 函数声明: int __sys_socket(int family, int type, int protocol) 定义了一个叫做 __sys_socket 的函数,接受三个参数:`family` 表示socket的地址族(如AF_INET),`type` 表示socket的类型(如SOCK_STREAM或SOCK_DGRAM),`protocol` 表示使用的协议(如IPPROTO_TCP)。
2. 函数内的局部变量声明:
   - int retval; 用来存储函数调用的返回值。
   - struct socket *sock; 定义一个指向 socket 结构体的指针变量。
   - int flags; 用于存储类型中的标志位。
3. 常量检查: BUILD_BUG_ON 宏用来在编译时检查某些情况是否真实,如果条件为真,编译将失败。此处检查了几个有关Socket的常量设置是否一致,以确保在编译时没有逻辑错误。
4. 标志处理:
   - 该函数初期根据 type 参数中的标志位获取 flags。
   - 检查 flags 是否包含除了 SOCK_CLOEXEC 和 SOCK_NONBLOCK 外的其他标志,如果包含则返回错误值 -EINVAL(表示参数无效)。
   - 然后,剔除 type 中的标志位,将其仅留下socket类型标志。
5. 非阻塞标志转换:
   - 如果 SOCK_NONBLOCK 和 O_NONBLOCK 不相同,但 flags 中的 SOCK_NONBLOCK 被设置了,那么就将 flags 中的 SOCK_NONBLOCK 转换为 O_NONBLOCK。
6. 创建socket:
   - 使用 sock_create 函数尝试创建一个socket,传入`family`, type, protocol 和一个指向 socket 结构体指针的地址。
   - 如果创建失败(即返回值小于0),则直接返回错误的返回值。
7. 返回文件描述符:
   - 如果socket创建成功,会使用 sock_map_fd 函数将socket映射到一个文件描述符,同时将 flags 中的 O_CLOEXEC 和 O_NONBLOCK 标志传递给该函数。
   - 函数最终返回一个文件描述符,该描述符可以用于后续的socket操作。

四、int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern)

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;// 确认传入的协议族 `family` 是否在有效范围中if (family < 0 || family >= NPROTO)return -EAFNOSUPPORT;// 确认传入的套接字类型 `type` 是否在有效范围中if (type < 0 || type >= SOCK_MAX)return -EINVAL;// 废弃的兼容性代码。如使用了SOCK_PACKET,将其转换为PF_PACKETif (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();if (!sock) {net_warn_ratelimited("socket: no more sockets\n");return -ENFILE; // 返回文件句柄不足的错误}sock->type = type;// 尝试加载协议族相关的模块(如果需要)if (rcu_access_pointer(net_families[family]) == NULL)request_module("net-pf-%d", family); // 请求加载协议族对应的模块(如果需要)rcu_read_lock();pf = rcu_dereference(net_families[family]);err = -EAFNOSUPPORT;if (!pf)goto out_release;// 尝试获取协议族模块的引用计数,如果获取失败则释放资源if (!try_module_get(pf->owner))goto out_release;// 如果获取成功,则调用该协议族的create方法来完成套接字的创建rcu_read_unlock();err = pf->create(net, sock, protocol, kern);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`*res = sock;return 0;out_module_busy:err = -EAFNOSUPPORT;
out_module_put:// 如果模块忙碌,清理并释放协议族模块的引用计数sock->ops = NULL;module_put(pf->owner);
out_sock_release:// 释放创建的套接字sock_release(sock);return err;out_release:// 释放读锁并释放套接字rcu_read_unlock();goto out_sock_release;
}
EXPORT_SYMBOL(__sock_create);

这段代码是一个内核函数 __sock_create,它用于在Linux内核中创建一个新的套接字。

这个函数定义了创建一个新套接字的流程,在网络编程中执行一个非常重要的角色。它主要执行以下步骤:
1. 检查是否传入了有效的 family(协议族,如IPv4或IPv6)和 type(套接字类型,如流或数据报)参数。如果参数超出了预定义的范围,函数会返回对应的错误码。
2. 旧版兼容性处理,如果使用的是PF_INET和SOCK_PACKET,就发出警告并将family改成PF_PACKET。
3. 调用安全模块中的 security_socket_create 函数来执行额外的安全检查。如果安全检查失败,它将返回一个错误码。
4. 尝试为新的套接字分配内存。如果没有足够的内存可供套接字使用,则返回一个错误码。
5. 如果上文中的某些协议家族尚不存在于 net_families 数组中(即该协议没有注册),则尝试动态加载对应的协议家族模块。
6. 获取对应的协议家族结构 pf,如果 pf 为NULL或无法获取其模块的引用,将释放资源并返回错误。
7. 如果协议模块的引用成功获取,则调用协议的 ->create 函数创建套接字。如果创建失败,释放相关资源并返回错误。
8. 协议族模块的作用已完成,模块引用计数减一。
9. 使用 security_socket_post_create 函数执行套接字创建后的安全检查,如果检查失败,进行资源清理并返回错误。
10. 如果一切顺利,将 sock 指针赋给输出参数 res,表示套接字创建成功,函数返回0。
异常处理标签 out_module_busy、`out_module_put`、`out_sock_release` 是用于错误发生时的资源清理工作。例如,释放套接字、减少模块引用计数等。
最后,`EXPORT_SYMBOL` 宏将 __sock_create 函数导出,这允许其他内核模块调用这个函数。

五、rcu_read_lock和rcu_read_unlock

rcu_read_lock 和 rcu_read_unlock 是在Linux内核中使用的一对函数,它们被用来保护使用读-复制更新(Read-Copy Update, RCU)机制的数据结构。这个机制允许多个读者并行访问数据结构,而写者则能够在不阻塞读者的情况下修改数据结构。
rcu_read_lock 的作用:
当一个内核线程调用 rcu_read_lock,它标记了一个临界区开始,这个临界区内的读者可以安全地读取RCU保护的数据结构,即使有其他线程正在更改这些数据结构。`rcu_read_lock` 通常不会阻塞调用它的线程,并且它并不保护数据结构免受更改,而是确保在临界区内读取的数据结构在读取时是一致的。在这个临界区内,任何持续对数据结构的更新操作都不会被看到,这保证了数据结构的读取视图是一致的。
rcu_read_unlock 的作用:
rcu_read_unlock 标记了RCU读临界区的结束。它告诉内核,线程已经完成对RCU保护数据的读取,在此之后对数据结构的更改可能对该线程可见。在`rcu_read_unlock` 调用之后,线程不再保证能访问之前RCU保护的数据结构的一致视图。
使用这对函数可以提高并发性能,因为它们消除了传统锁机制造成的读者与写者之间的竞争,允许大量并行的读取操作,而不会与可能并发发生的写入操作冲突。然而,写入操作需要使用专门的RCU API来确保数据结构的更改能够被安全地发布给未来的读者。

六、static int sock_map_fd(struct socket *sock, int flags)

这个函数`sock_map_fd`是一个静态函数,它的主要作用是把一个已创建的套接字(struct socket)映射到一个文件描述符上。以下是该函数的步骤和解释:

static int sock_map_fd(struct socket *sock, int flags)
{struct file *newfile; // 定义一个用于文件描述符的指针变量int fd = get_unused_fd_flags(flags); // 获取一个未使用的文件描述符if (unlikely(fd < 0)) { // 如果获取失败(例如因为没有可用描述符)sock_release(sock); // 释放之前分配的socket资源return fd; // 返回错误码(负值)}newfile = sock_alloc_file(sock, flags, NULL); // 为socket分配一个file结构if (likely(!IS_ERR(newfile))) { // 如果分配成功fd_install(fd, newfile); // 把这个新的file结构和之前获得的描述符fd关联起来return fd; // 返回这个新的文件描述符}put_unused_fd(fd); // 如果分配失败,则释放之前获得的文件描述符return PTR_ERR(newfile); // 返回对应的错误码
}

函数首先通过调用`get_unused_fd_flags(flags)`获得一个未被使用的文件描述符(fd)。如果无法获得有效的文件描述符(例如所有的描述符都已被占用),该函数使用`sock_release(sock)`来释放套接字资源,并且返回一个错误码。
如果成功获取到一个有效的文件描述符,函数接着会尝试使用`sock_alloc_file(sock, flags, NULL)`来为套接字分配一个对应的`file`结构体。如果这个分配过程成功则没有出错,它会将这个新的`file`结构体和之前获得的文件描述符关联起来,使用`fd_install(fd, newfile)`完成这个操作。
如果`file`结构体分配失败(例如内存不足),函数会释放之前获得的文件描述符`fd`,使用`put_unused_fd(fd)`,并返回分配`file`时的错误码。
最后,如果所有操作都成功了,这个函数会返回一个正整数,它是新创建的可以用于操作套接字的文件描述符。如果操作失败,它会返回一个错误码用来表示具体何种错误发生。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/461788.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

24 SEMC相关

文章目录 24.1 SEMC 简介24.2 SEMC 框图剖析24.2.1 通讯引脚24.2.1.1 SEMC 的片选信号24.2.1.2 数据选通信号 DQS 24.2.2 存储器控制器24.2.3 IP 命令和 AXI 命令24.2.4 驱动时钟 24.4 SEMC 初始化配置结构体24.4.1 dqsMode24.4.2 cmdTimeoutCycles24.4.3 busTimeoutCycles24.4…

【C语言自定义类型详解进阶】结构体(补充结构体的对齐和位段,一口气看完系列,央妈都点赞的博文)

目录 1.结构体 1.1 结构的基础知识 1.2 结构的声明 1.2.1特殊的声明&#xff08;匿名结构体类型&#xff09; 1.3结构体变量的定义 1.4关于匿名结构体类型的补充 1.5结构体的自引用 1.6结构体变量的初始化 2.结构体内存对齐&#xff08;重点&#xff09; 2.1偏移量补…

MySQL篇----第十六篇

系列文章目录 文章目录 系列文章目录前言一、数据库中的事务是什么?二、SQL 注入漏洞产生的原因?如何防止?三、为表中得字段选择合适得数据类型四、存储时期前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇…

Qt PCL学习(二):点云读取与保存

注意事项 版本一览&#xff1a;Qt 5.15.2 PCL 1.12.1 VTK 9.1.0前置内容&#xff1a;Qt PCL学习&#xff08;一&#xff09;&#xff1a;环境搭建 0. 效果演示 1. pcl_open_save.pro QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgets// 添加下行代码&#…

昆仑万维发布天工 2.0 大语言模型及AI助手App;AI成功破解2000年前碳化古卷轴

&#x1f989; AI新闻 &#x1f680; 昆仑万维发布天工 2.0 大语言模型及AI助手App 摘要&#xff1a;昆仑万维近日推出了新版MoE大语言模型“天工 2.0”和相应的“天工 AI 智能助手”App&#xff0c;宣称为国内首个面向C端用户免费的基于MoE架构的千亿级参数大模型应用。天工…

Javaweb之SpringBootWeb案例之异常处理功能的详细解析

3. 异常处理 3.1 当前问题 登录功能和登录校验功能我们都实现了&#xff0c;下面我们学习下今天最后一块技术点&#xff1a;异常处理。首先我们先来看一下系统出现异常之后会发生什么现象&#xff0c;再来介绍异常处理的方案。 我们打开浏览器&#xff0c;访问系统中的新增部…

【Java八股面试系列】JVM-常见参数设置

目录 堆内存相关 显式指定堆内存–Xms和-Xmx 显式新生代内存(Young Generation) 显式指定永久代/元空间的大小 垃圾收集相关 垃圾回收器 GC 日志记录 处理 OOM JDK监控和故障处理工具总结 堆内存相关 Java 虚拟机所管理的内存中最大的一块&#xff0c;Java 堆是所有线…

ncc匹配(五,匹配提速的思考)

感觉ncc&#xff08;相关系数匹配&#xff09;与bpnet&#xff08;bp神经网络&#xff09;相似&#xff0c;但ncc简洁方便快速&#xff0c;计算量小&#xff0c;问题点也少。 都有归一化的动作&#xff0c;都是相关性的学习&#xff0c;不过bpnet可以学习多种类型&#xff0c;…

机器学习10-特征缩放

特征缩放的目的是确保不同特征的数值范围相近&#xff0c;使得模型在训练过程中更加稳定&#xff0c;加速模型收敛&#xff0c;提高模型性能。具体而言&#xff0c;零均值和单位方差的目标有以下几点好处&#xff1a; 1. 均值为零&#xff08;Zero Mean&#xff09;&#xff1a…

University Program VWF仿真步骤__全加器

本教程将以全加器为例&#xff0c;选择DE2-115开发板的Cyclone IV EP4CE115F29C7 FPGA&#xff0c;使用Quartus Lite v18.1&#xff0c;循序渐进的介绍如何创建Quartus工程&#xff0c;并使用Quartus Prime软件的University Program VWF工具创建波形文件&#xff0c;对全加器的…

Linux——进程池(管道)

经过了管道的介绍之后&#xff0c;我们可以实现了进程间通信&#xff0c;现在我就来简单介 绍一下管道的应用场景——进程池。1. 引入 在我们的编码过程中&#xff0c;不乏会听到&#xff0c;内存池&#xff0c;进程池&#xff0c;空间配置器等等名词&#xff0c;这些是用来干…

PV、UV、IP

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1. PV1.1 PV 计算1.2 PV 的影响因素 2. UV2.1 UV 计算2.2UV 的影响因素 3. IP3.1 IP和UV①UV大于IP②UV小于IP 三者的关系PV 和 UV 前言 PV、UV、IP是我们在运…