LWN:PID的复仇!以及process_madvise(), pidfd capabilities

在Linux中,process_madvise()系统调用允许一个进程控制另一个进程的内存管理,作为madvise()的扩展,它支持如MADV_COLD和MADV_PAGEOUT等建议类型,用于优化内存使用。此调用需通过pidfd进行,引发对pidfd capabilities机制的讨论,以更细粒度地控制进程间操作权限。

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

process_madvise(), pidfd capabilities, and the revenge of the PIDs

By Jonathan Corbet
January 21, 2020

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

从前,在某个进程被创建之后,其他进程就很难有办法对它进行操作了,只有给它发signal或者用ptrace()。现在,人们越来越有需求要能控制其他进程了,相应地,kernel的进程管理API也得到了扩展。process_madvise()系统调用就是其中一种,用来控制另一个进程内部内存管理方式的。这个最近提出来的一个process_ madvise(),尽管它有其合理性,但是也引起了许多人的提问,都是有关今后进程管理方式该如何改进的。

现有的madvise()系统调用是供某个进程来告诉kernel,关于他自己的地址空间该如何管理的。5.4的内核的madvise()调用中就多了一些新的建议类型:MADV_COLD和MADV_PAGEOUT。前者要求kernel把这块区域的内存页放到inactive list上去,也就是说它们已经挺长时间没有被访问过了。这些页面在后续内核进行内存回收的时候就会优先回收。MADV_PAGEOUT则语气更强烈一些,直接就说这些页面不会再用了,因此会导致马上被回收。

对于那些非常了解自己未来的访问模式的进程来说,这两个新类型会很有用处。不过在某些场景下——例如Android——进程其实并不清楚这个信息。在某些内存不再需要使用的情况,只有系统中的管理进程知道。比如说,在某个前台进程被放到后台的时候,这个进程的许多地址空间就可以标记成MADV_COLD了。这种环境中,要想最大限度地利用系统的内存资源,就需要让某个进程来替其他进程调用madvise()。这就是为什么要有process_madvise()的原因了。

Minchan Kim在1月初的时候就提过一组process_madvise() patch,当时的API是这样的:

    int process_madvise(int pidfd, void *addr, size_t length, int advice,
    			unsigned long flags);

效果上就像是pidfd所代表的这个进程自己调用了madvise()一样,参数也是addr, length, 和advice。这里的flags参数其实没有用,并且只支持madvise()的MADV_系列参数的一个子集(MADV_COLD,MADV_PAGEOUT,MADV_MERGEABLE,MADV_UNMERGEABLE)。其他的参数计划等到确有需要的时候再加。

从目前为止关于process_madvise()的讨论来看,基本没有什么阻碍,很快能进入mainline了。不过接下来,就有一些问题跳了出来。

Pidfd capabilities

其中一个问题跟process_madvise()并不直接相关,而是跟pidfd API通常用法有关。目前的patch set里面,是否能允许一个进程调用process_madvise()来操作另一个进程,是通过问一个问题来确定的:“是否允许用ptrace()?”。所以,这就意味着要么是同一个user ID的两个进程,要么就是拥有CAP_SYS_PTRACE的权限。这个判断问题本身并没有什么争议,不过Christian Brauner(pidfd patch的主要作者)提出了另一个想法:

When you create a pidfd, e.g. with clone3() and you'd wanted to use it for madvise you'd need to set a flag like pidfd_cap_madvise or pidfd_feature_madvise when you create the pidfd. Only if the pidfd was created with that flag set could you use it with madvise.

简单来说,pidfd自己包含了一个capability mask,来指定可以对它进行什么操作。这就像是通常的文件描述符一样:人们打开文件的时候如果允许了写操作,那么就可以对这个文件描述符进行写操作。Brauner希望在5.7的时候再合入process_madvise(),这样它就能直接开始使用这个capabilty机制了(目前还没实现好)。

关于pidfd的capability工作机制,Daniel Colascione问了个有趣的问题:是不是pidfd带有相应的capability flag就可以进行这个操作了,还是此前的传统判断方式也可以继续用?也就是说,加入如果一个进程拿到了另一个进程的pidfd,并且pidfd已经允许它传递给process_madvise()了,那么这个进程是否还需要试着进行ptrace()来判断是否可以操作?

Brauner在这个问题上有过反复。他说他倾向仍然需要检查权限,不完全依赖pidfd的capability flag。这样这些capability flag就变成了一个关闭开关,用来限制哪些操作是被禁止的。Colascione更希望直接用pidfd的capability flag来判断权限就够了。Brauner答应会仔细考虑这个方案,近期会发出patch。

PIDs: not dead yet

这个process_madvise() API的另一个特点是它必须要使用pidfd,这一点跟其他使用process ID的kill()或setpriority()等系统调用不同。pidfd是这个领域的新宠,通常来说大家都倾向于用pidfd。pid在哥哥namespace里面可能会不同,并且在目标进程退出之后,有一定的概率被用在另一个完全不相关的进程上;而pidfd则可以确定指向某个进程,不会变动。因此,pidfd很快变成了所有新增的系统调用中最受欢迎的确认进程的方式。Colascione的说法是:“All new APIs should use pidfds: they're better than numeric PIDs in every way”。

不过,看来并不是所有人都赞同这个观点。很多场景下pid本来就是已知的,而pidfd还需要专门去创建出来。用户还可以在命令行上指定某个pid,而pidfd则没法这么用。Kirill Tkhai就是其中一个反对者:

Ordinary pid interfaces also should be available. There are a lot of cases, when they are more comfortable. Say, a calling of process_madvise() from tracer, when a tracee is stopped. In this moment the tracer knows everything about tracee state, and pidfd brackets pidfd_open() and close() around actual action look just stupid, and this is cpu time wasting.

因此他认为每个新增的进城相关API都应该要既能处理PID,又能处理pidfd。Kim很认真地吸取了这个提议,在一周后的第二版process_madvise() patch set中就实现好了。这样新增的API就变为了:

    int process_madvise(int which, pid_t pid, void *addr, size_t length,
    			int advice, unsigned long flag);

这里新加的which参数要么是P_PID,要么是P_PIDFD,用来告诉kernel该如何理解pid参数。

这一改动提醒所有人都要注意一个问题。如果大家达成一致,都认为新增的进程相关API都应该同时支持这两种进程表达方式,那么就应该把这个规则推广开来,对新增的API一视同仁。否则process_madvise()也就不应该被强制要求这么做。如果今后某些API只支持一种方式,另一些API则两种都支持,那么就会是最坏的情况了。Linux的系统调用API目前已经有这样那样的一致性问题了,如果我们继续新增一些API让这个情况变得更差,未来的开发者会怎么看待我们?

全文完

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

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

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

<think> 嗯,用户这次的问题非常具体,是关于kernel_device_modules-6.6构建过程中mgk_64_k66_customer_modules_install.user失败的解决方案。从问题描述看,用户应该是嵌入式Linux开发工程师,正在定制内核模块,遇到构建中断的问题。 上次对话中用户提到了“kernel_device_modules-6.6”这个关键词,说明是在处理内核设备模块的编译问题。这次报错文件扩展名.user比较特殊,像是某种自定义构建脚本。用户能准确提供文件名,说明已经有一定技术基础,可能排查过基础路径问题。 需要特别注意几个关键点:一是“kernel_device_modules-6.6”这个项目名称看起来像商业嵌入式方案,可能是客户定制内核;二是“mgk_64_k66”这个前缀,mgk可能是模块组缩写,64指64位架构,k66疑似指NXP的Kinetis K66微控制器;三是构建脚本后缀.user比较罕见,可能是企业内部自定义的构建系统。 构建失败通常有几类原因:环境配置问题(如交叉编译器缺失)、脚本逻辑错误(路径处理不当)、依赖缺失(头文件或库文件)、权限问题等。考虑到.user脚本的特殊性,应该优先检查:1)执行权限;2)脚本内部路径是否正确指向内核源码目录;3)交叉编译工具链是否配置。 用户没有提供具体错误日志是最棘手的。需要引导用户提供更多信息,比如构建时的终端输出、脚本开头部分的路径配置等。另外要注意这个模块可能是依赖特定内核配置选项的,需要确认.config文件中相关驱动是否启用。 最后要提醒用户注意风险——这类定制内核模块的构建失败往往涉及多方因素,企业内部的构建系统可能还有非公开依赖。建议用户先联系原厂支持渠道,同时提供尽可能详细的错误信息方便进一步排查。 </think> 针对 `kernel_device_modules-6.6` 中 `mgk_64_k66_customer_modules_install.user` 构建失败的解决方案,请按以下步骤排查: --- ### **排查步骤** #### 1. **检查构建环境** - **内核源码完整性** 确认 `kernel_device_modules-6.6` 源码完整且与当前运行内核版本一致: ```bash uname -r # 查看当前内核版本 ls /lib/modules/$(uname -r)/build # 验证内核头文件存在 ``` 若路径缺失,需安装对应内核头文件包[^1]。 - **交叉编译工具链** 若为嵌入式设备(如 `k66` 可能指 NXP Kinetis K66),检查交叉编译器是否配置: ```bash echo $CC # 查看环境变量 arm-none-eabi-gcc --version # 示例(具体工具链名称需确认) ``` #### 2. **分析构建日志** - 查找错误关键字(如 `Error`/`Failed`): ```bash grep -i "error\|failed" build.log # 替换为实际日志文件名 ``` **常见问题方向**: - **依赖缺失**:如 `undefined reference` 提示缺少库或头文件 - **语法错误**:内核 API 变更导致函数签名不兼容 - **权限问题**:`Permission denied` 提示需 `sudo` 或修复路径权限 #### 3. **验证脚本权限与路径** - 添加可执行权限: ```bash chmod +x mgk_64_k66_customer_modules_install.user ``` - 检查脚本内路径是否为**绝对路径**(避免相对路径嵌套错误): ```bash head -n 20 mgk_64_k66_customer_modules_install.user # 查看脚本头部配置 ``` #### 4. **依赖项检查** - 安装基础编译依赖: ```bash sudo apt-get install build-essential libssl-dev flex bison libelf-dev ``` - 确认模块特殊依赖(如厂商 SDK、闭源驱动)是否部署。 #### 5. **兼容性调整** - **内核符号导出问题**: 若日志含 `Unknown symbol`,需在模块代码中添加导出声明: ```c EXPORT_SYMBOL(your_function); // 内核源码中声明 ``` - **版本适配**: 若内核 ≥5.7,需替换弃用的 `get_user_pages()` 为 `get_user_pages_remote()`[^2]。 #### 6. **增量构建测试** - 分步编译定位故障点: ```bash make -C /lib/modules/$(uname -r)/build M=$(pwd) modules # 仅编译模块 sudo insmod your_module.ko # 手动加载测试 ``` --- ### **典型错误场景与修复** | **错误类型** | **解决方案** | |------------------------|------------------------------------------| | **`Makefile: No such file`** | 确认脚本中 `KERNEL_SRC` 路径正确指向内核源码目录 | | **`Invalid module format`** | 强制编译目标版本:`make KERNELRELEASE=$(uname -r)` | | **`undefined reference`** | 检查模块依赖链,添加 `obj-m += dependent_module.o` | | **`Function not implemented`** | 替换新版内核 API(如 `vmalloc_user()` 替代旧接口) | --- ### **补充建议** 1. **联系厂商支持**: `mgk_64_k66` 可能是定制模块,需获取厂商提供的补丁或更新脚本。 2. **版本回退测试**: 尝试旧内核版本(如 5.4 LTS)验证是否兼容性问题。 3. **调试符号分析**: 启用 `CONFIG_DEBUG_INFO=y` 后使用 `dmesg` 查看详细崩溃堆栈。 > 请优先提供 **完整构建日志** 以精准定位问题。若上述步骤未解决,建议提交日志到社区论坛(如 [Stack Overflow](https://stackoverflow.com))或内核邮件列表[^3]。 --- [^1]: 内核头文件安装参考:[Ubuntu Kernel Headers](https://wiki.ubuntu.com/Kernel/BuildYourOwnKernel) [^2]: 内核 API 变更说明:[Linux 5.7 API Changes](https://lwn.net/Articles/817101/) [^3]: 内核社区支持:[LKML (Linux Kernel Mailing List)](https://lkml.org/)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值