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

2024-02-09 11:12

本文主要是介绍【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`时的错误码。
最后,如果所有操作都成功了,这个函数会返回一个正整数,它是新创建的可以用于操作套接字的文件描述符。如果操作失败,它会返回一个错误码用来表示具体何种错误发生。

这篇关于【C语言】SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

C 语言的基本数据类型

C 语言的基本数据类型 注:本文面向 C 语言初学者,如果你是熟手,那就不用看了。 有人问我,char、short、int、long、float、double 等这些关键字到底是什么意思,如果说他们是数据类型的话,那么为啥有这么多数据类型呢? 如果写了一句: int a; 那么执行的时候在内存中会有什么变化呢? 橡皮泥大家都玩过吧,一般你买橡皮泥的时候,店家会赠送一些模板。 上

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla