【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://xiahunao.cn/news/2775835.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Android开发-之屏幕适配

Android开发-之屏幕适配 前言 Android 系统能发展到今天&#xff0c;离不开其开源性&#xff0c;但是随着越来越多的设备接入 Android 系统&#xff0c;并对 Android 系统进行各种各样的定制&#xff0c;导致长期以来出现了各种碎片化严重的问题。例如&#xff0c;Android 屏…

【新书推荐】7.2 while语句

本节必须掌握的知识点&#xff1a; 掌握if语句语法 熟练使用if语句 7.2.1 示例二十三 ■while语句其语法形式&#xff1a; while(表达式) { 语句块&#xff1b; } ●语法解析&#xff1a; 第一步&#xff1a;执行表达式&#xff0c;如果表达式为真&#xff0c;则执行第…

【代码】Processing笔触手写板笔刷代码合集

代码来源于openprocessing&#xff0c;考虑到国内不是很好访问&#xff0c;我把我找到的比较好的搬运过来&#xff01; 合集 参考&#xff1a;https://openprocessing.org/sketch/793375 https://github.com/SourceOf0-HTML/processing-p5.js/tree/master 这个可以体验6种笔触…

Netty连接通道中的Channel参数模型

ChannelOption(Channel中的连接参数) ChannelOption.SOBACKLOG ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数&#xff0c;服务端处理客户端连接请求是顺序处理的&#xff0c;所以同一时间只能处理一个客户端连接&#xff0c;多个客户端来的时候&…

P1297 [国家集训队] 单选错位 对期望的理解

[国家集训队] 单选错位 - 洛谷 思路&#xff1a; 其实每个位置的得分只和前一个位置有关。 而他们俩的所有情况的期望就是答案的这部分。 ——这是难想的&#xff0c;我期望学的不好。 &#xff08;题目给的是每种情况的所有位置的和&#xff0c;全加起来是答案&#xff1…

【数据分享】1929-2023年全球站点的逐月平均风速(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全球气象站…

ansible shell模块 可以用来使用shell 命令 支持管道符 shell 模块和 command 模块的区别

这里写目录标题 说明shell模块用法shell 模块和 command 模块的区别 说明 shell模块可以在远程主机上调用shell解释器运行命令&#xff0c;支持shell的各种功能&#xff0c;例如管道等 shell模块用法 ansible slave -m shell -a cat /etc/passwd | grep root # 可以使用管道…

边缘计算初创公司ZEDEDA获7200万美元C轮融资,助力边缘计算市场扩张!

边缘计算领域传来喜讯&#xff01; 边缘计算社区获悉&#xff0c;就在昨天&#xff08;2月7日&#xff09;&#xff0c;边缘计算企业ZEDEDA成功募集7200万美元C轮融资&#xff0c;折合人民币高达约5.18亿元。据悉&#xff0c;此次融资使ZEDEDA的估值飙升至4亿美元&#xff0c;相…

Java:内部类、枚举、泛型以及常用API --黑马笔记

内部类 内部类是类中的五大成分之一&#xff08;成员变量、方法、构造器、内部类、代码块&#xff09;&#xff0c;如果一个类定义在另一个类的内部&#xff0c;这个类就是内部类。 当一个类的内部&#xff0c;包含一个完整的事物&#xff0c;且这个事物没有必要单独设计时&a…

git flow与分支管理

git flow与分支管理 一、git flow是什么二、分支管理1、主分支Master2、开发分支Develop3、临时性分支功能分支预发布分支修补bug分支 三、分支管理最佳实践1、分支名义规划2、环境与分支3、分支图 四、git flow缺点 一、git flow是什么 Git 作为一个源码管理系统&#xff0c;…

JavaScript鼠标移动事件

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 鼠标移动是用户界面中非常重要的交互行为。学习区分不同的鼠标移动事…

PySpark(三)RDD持久化、共享变量、Spark内核制度,Spark Shuffle、Spark执行流程

目录 RDD持久化 RDD 的数据是过程数据 RDD 缓存 RDD CheckPoint 共享变量 广播变量 累加器 Spark 内核调度 DAG DAG 的宽窄依赖和阶段划分 内存迭代计算 Spark是怎么做内存计算的? DAG的作用?Stage阶段划分的作用? Spark为什么比MapReduce快&#xff1f; Spa…

机器学习10-特征缩放

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

红队打靶练习:GLASGOW SMILE: 1.1

目录 信息收集 1、arp 2、nmap 3、nikto 4、whatweb 目录探测 1、gobuster 2、dirsearch WEB web信息收集 /how_to.txt /joomla CMS利用 1、爆破后台 2、登录 3、反弹shell 提权 系统信息收集 rob用户登录 abner用户 penguin用户 get root flag 信息收集…

【Python】使用 requirements.txt 与 pytorch 相关配置

【Python】使用 requirements.txt 与 pytorch 相关配置 前言一、pip1、导出结果含有路径2、导出不带路径的 二、Conda1、导出requirements.txt2、导出yml 文件 三、第三方包&#xff1a;pipreqs&#xff08;推荐&#xff09;1、创建并激活conda环境2、安装requirements文件的pi…

Linux进程间通信(2)--System V共享内存 | 消息队列 | 信号量

目录 实现原理 使用系统调用创建共享内存 使用shmget函数创建共享内存&#xff1a; 使用shmat函数将共享内挂接到进程地址空间的共享区 使用shmdt函数取消共享内存与进程地址空间的关联 ​编辑 使用shmctl函数释放共享内存 共享内存的属性 System V消息队列 &#xff…

文件上传-Webshell

Webshell简介 webshell就是以aspphpjsp或者cgi等网页文件形式存在的一种命令执行环境&#xff0c;也可以将其称做为一种网页木马后门。 攻击者可通过这种网页后门获得网站服务器操作权限&#xff0c;控制网站服务器以进行上传下载文件、查看数据库、执行命令等… 什么是木马 …

docker 基于容器创建本地web容器化镜像

一、docker 基于容器创建本地web容器化镜像 1、启动指定buysbox 镜像 docker run --name b1 -it busybox:latest 2、创建目录&#xff0c;并创建html mkdir -p /data/html vi index.html 内容自定义例如&#xff1a;<h1>welcome to busybox<h1> 3、新增窗口&am…

类和对象 第六部分第二小节:继承方式

前情提要&#xff1a;继承的语法&#xff1a;class 子类 继承方式&#xff1a;父类 继承方式一共有3种&#xff1a; 代码案例&#xff1a; 前置代码 #include<iostream> using namespace std; class Base1 { public:int m_A; protected:int m_B; private:int m_C; };公共…

linux C编程入门

Ubuntu 下也有一些可以进行编程的工具&#xff0c;但是大多都只是编辑器&#xff0c; 也就是只能进行代码编辑&#xff0c;如果要编译的话就需要用到 GCC 编译器&#xff0c;使用 GCC 编译器肯定就 要接触到Makefile。 1&#xff1a;hello world!!! 我们所说的编写代码包括两部…