LWN: 利用pidfd_getfd()来抢夺文件描述符

为了增强进程间文件描述符的控制能力,Linux内核引入了pidfd_getfd()系统调用,使一个进程能够直接复制另一个进程已打开的文件描述符,包括管道和套接字等特殊文件,而无需创建新的文件表条目。这一功能对于实现高级进程管理和资源接管具有重要意义。

关注了就能看到更多这么棒的文章哦~

Grabbing file descriptors with pidfd_getfd()

By Jonathan Corbet
January 9, 2020

原文来自:https://lwn.net/Articles/808997/

从user space对一组进程进行控制的需求越来越多,因此kernel增加了一些机制可以让一个进程操作另一个进程。不过拼图中还缺少一块,目前一个进程还是无法从另一个进程中复制已经打开的文件描述符。Sargun Dhillon的pidfd_getfd()系统调用patch set合入之后,kernel就可以拥有这个功能了。

现有的kernel里面已经支持某个进程打开另一个进程中已经处于打开状态的文件了。这是通过访问每个进程的/proc目录来实现的。不过,如果目标文件描述符是用于管道、套接字(socket)等等未能出现在/proc目录下的内容的话,这个方法就无法利用了。并且,采用这种方法会在file table里面创建新的条目,这个新条目并不是对应我们所关注进程中的文件描述符的。

如果我们的目标是希望直接修改那个特定的文件描述符的话,上述限制也就阻碍了我们想做的事情。这个patch set中举了一个例子来说明,就是其他用户试图把某个socket给bind到某个特权端口号的时候,我们试图使用seccomp来截获此bind申请。拥有响应特权的监管者进程可以先取得目标进程的socket对应的文件描述符,然后直接帮它进行bind操作(目标进程本身其实无权进行这个操作)。因为获取到的文件描述符跟原本哪个本质上来说是相同的,所以bind操作对目标进程也是可见的。

其实现在就是有办法从另一个进程提取文件描述符的。就是利用ptrace()来attach到目标进程,让它停止执行,然后注入代码让它可以跟管理员进程进行通信并通过SCM_RIGHTS格式来把文件描述符发送过去,然后继续运行原来代码。这个方案可行,可以就是不太优雅。它也会暂停目标进程的执行,这一点许多人不喜欢。

其实如果不希望暂停目标进程的话,在kernel还是很容易可以实现的。这个监管者进程只需要简单调用一下:

    int pidfd_getfd(int pidfd, int targetfd, unsigned int flags);

pidfd参数就是用来指定目标进程的,它是在进程创建时获得的pidfd信息。需要获取的文件描述符是通过targetfd来指定的。一切正常的话,返回值是对应这个目标进程中特定文件的一个文件描述符需要。不过前提条件是调用此函数的进程要有权限对目标进程调用ptrace()。

flags这个参数目前没有用,必须传入0。后续会需要增加flags的含义。比如可以用一个flag标记来在目标进程这边在文件描述符被复制给调用此API的进程之后就关闭掉,这样就真正实现了从目标进程把文件描述符彻底接手过来的效果。还有一个预计可以加的flag是针对socket描述符的,在copy过程中移除cgroup数据。

这组patch set自从12月5日第一次发布之后经过了许多版迭代,变化很大。最早的一版是为ptrace()新增了一个PTRACE_GETFD命令。第3版切换成了针对pidfd的一个ioctl()操作。从首发之后的第15天就提出了第5版,使用了一个新增的系统调用来完成这个功能。目前最新版本是第9版。

一直一来大家对这个功能要达到的目标都没有什么异议。review中的意见都是针对实现方式的。目前来看Dhillon基本已经把所有可能的实现方案都尝试了一遍,不过其实还有人认为后续还需要用BPF来实现一版。如果没有这些意外的话,这个新增的系统调用应该能合并入5.6或者5.7版本中。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; int battery_type_check = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug( "[battery_type_check] read channel err = %d,\n", ret); bm_debug( "[battery_type_check]: ret = %d,ret_value[%d]\n", ret, ret_value); value = ret_value; if(is_fuelgauge_apply() == true) { switch(battery_id) case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default:【兼容原来的方案】 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; }
08-21
这段代码的主要功能是检测电池类型,通过读取电池的ADC(模数转换)值来判断使用的是哪种类型的电池。以下是代码的详细解释: ### 代码功能说明: 1. **变量定义**: - `value`:存储读取到的ADC值。 - `ret`:存储读取ADC值的返回值(用于判断是否读取成功)。 - `ret_value`:从ADC通道读取的原始值。 - `battery_id`:表示电池的ID。 - `battery_type_check`:用于判断电池类型的变量。 2. **空指针检查**: 如果 `batt_id` 为 NULL,则将电池类型设置为默认值 `BAT_TYPE__ATL_4400mV`,并将 `battery_id` 设置为 0。 3. **读取ADC值**: 使用 `iio_read_channel_processed` 函数读取ADC值,并进行错误检查。 4. **电池类型判断**: 根据 `battery_id` 的值,使用 `switch-case` 结构判断电池类型。每个 `case` 对应一个电池类型,并检查ADC值是否在该类型的范围内。 5. **默认情况处理**: 如果 `battery_id` 不在预设范围内,则进入 `default` 分支,尝试匹配其他电池类型的ADC范围。 6. **日志输出**: 使用 `printk` 输出调试信息,包括ADC值、电池类型和电池ID。 ### 问题分析: 1. **语法错误**: - `switch-case` 结构的语法不正确,`case` 分支应该直接跟在 `switch` 后面,而不是嵌套在 `if` 语句中。 - `default:` 后面的冒号应该是英文冒号 `:`,而不是中文冒号 `:`。 2. **逻辑错误**: - `battery_id` 变量在 `switch-case` 结构中被用作判断条件,但它的值在代码中没有被正确更新或传递。 3. **代码结构问题**: - `switch-case` 结构应该放在 `if(is_fuelgauge_apply() == true)` 条件之外,或者需要重新设计逻辑以确保正确执行。 ### 改进后的代码: ```c int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug("[battery_type_check] read channel err = %d,\n", ret); bm_debug("[battery_type_check]: ret = %d, ret_value[%d]\n", ret, ret_value); value = ret_value; if (is_fuelgauge_apply() == true) { switch (battery_id) { case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default: // 兼容原来的方案 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } break; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; } ``` ### 代码改进说明: 1. **语法修正**: - 修复了 `switch-case` 结构的语法错误,确保每个 `case` 分支正确嵌套在 `switch` 语句中。 - 修正了 `default` 分支的冒号符号。 2. **逻辑优化**: - 确保 `battery_id` 在 `switch-case` 结构中被正确使用,并根据ADC值更新电池类型。 3. **代码结构优化**: - 保持代码结构清晰,确保 `if(is_fuelgauge_apply() == true)` 条件下的逻辑正确执行。 ### 相关问题: 1. 如何在Linux内核中实现ADC读取? 2. 如何设计电池类型的ADC阈值? 3. 如何调试和优化电池类型检测逻辑? 4. 如何处理电池类型检测中的异常情况?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值