php方案 php内核通信: 利用 FFI 直接调用 ioctl 操控自定义 Linux 内核模块

张开发
2026/4/3 13:37:07 15 分钟阅读
php方案 php内核通信: 利用 FFI 直接调用 ioctl 操控自定义 Linux 内核模块
两部分内核模块CPHPFFI调用。---内核模块 mydev.c 注册/dev/mydevioctl 收到一个int乘以2返回。#include linux/module.h#include linux/miscdevice.h#include linux/uaccess.h#define MYDEV_MAGIC k#define MYDEV_CMD _IOWR(MYDEV_MAGIC, 1, int)staticlongmydev_ioctl(struct file*f,unsignedintcmd,unsigned long arg){intval;if(cmd!MYDEV_CMD)return-EINVAL;if(copy_from_user(val,(int__user*)arg,sizeof(val)))return-EFAULT;val*2;returncopy_to_user((int__user*)arg,val,sizeof(val))?-EFAULT:0;}staticstruct file_operations fops{.unlocked_ioctlmydev_ioctl};staticstruct miscdevice mydev{MISC_DYNAMIC_MINOR,mydev,fops};module_init(({returnmisc_register(mydev);}));// 用宏简写module_exit(({misc_deregister(mydev);}));MODULE_LICENSE(GPL);▎ 用 miscdevice 省掉手动 register_chrdevclass_create 那一堆。---PHP调用 call.php?php$ffiFFI::cdef( int open(const char *path, int flags); int ioctl(int fd, unsigned long req, ...); int close(int fd); ,libc.so.6);// 手动算 _IOWR(k, 1, sizeof(int))// 位布局: [31:30]方向(3读写) [29:16]size [15:8]type [7:0]nr$cmd(330)|(416)|(ord(k)8)|1;$fd$ffi-open(/dev/mydev,2);// O_RDWR 2if($fd0)die(open failed\n);$bufFFI::new(int);$buf-cdata21;$ffi-ioctl($fd,$cmd,FFI::addr($buf));echo$buf-cdata. ;// 42$ffi-close($fd);---大白话 内核侧miscdevice 是最省事的注册方式自动创建/dev/mydev只需实现 unlocked_ioctl 回调。PHP侧FFI直接绑 libcopen()拿文件描述符FFI::new(int)分配共享内存缓冲区FFI::addr()取指针传给 ioctl。_IOWR那个数内核头文件里就是几个位移拼起来PHP里手算一遍等价不需要额外库。---编译运行# 内核模块make-C/lib/modules/$(uname-r)/buildM$PWDmodules sudo insmod mydev.ko# PHP (需要 ffi 扩展)sudo php call.php# 输出 42一句话终极结论 自己用C写一个极简的Linux内核小插件在系统里造出/dev/mydev 这个设备负责“收数字×2返回” 再用PHP通过FFI直接调用系统底层函数跟这个内核设备通信传21进去拿到结果42。 下面全程大白话拆碎讲懂每一部分。 一、内核模块mydev.c到底干啥 这是一段跑在内核里的C代码相当于给Linux内核装个迷你小功能不用重启系统就能加载。1.为啥用 miscdevice Linux要造一个设备比如/dev/xxx 原本要写一大堆注册代码超级麻烦。 miscdevice 是懒人专用简化版-自动帮你注册设备-自动生成/dev/mydev 这个文件-不用管复杂的内核设备规则一行代码搞定2.内核核心逻辑就干一件事 内核里实现了 ioctl 接口设备的专属指令入口1.从外部程序PHP拿到一个整数2.把这个数 乘以23.把结果放回给外部程序3.两个关键拷贝函数大白话 内核有自己的内存PHP有自己的内存两边不能直接互相读写必须用专用函数搬运-copy_from_user 把PHP传过来的数字搬到内核内存里-copy_to_user 把内核算好的×2结果搬回PHP的内存里4.那个命令码MYDEV_CMD就是内核和PHP约定的“暗号”-代表“这是乘2的指令”-由「读写方向数据大小设备标识命令号」按内核规则拼出来-两边暗号对得上才会执行逻辑 二、PHP代码call.php到底干啥PHP没有内置的内核设备调用扩展所以用FFI直接调用Linux系统底层函数跟内核设备对话。1.绑定系统函数 告诉PHP要调用3个系统底层功能-open 打开/dev/mydev 设备-ioctl 给设备发指令、传数据-close 用完关闭设备2.手动算“暗号”命令码 内核里的_IOWR是按位运算拼的命令码PHP里直接按同样规则算一遍和内核暗号对齐。3.核心交互流程1.打开/dev/mydev 设备必须root不然没权限2.建一个整数变量存数字213.把这个变量的地址传给 ioctl4.内核拿到21→乘2变成42→写回这个变量5.PHP打印变量输出426.关闭设备结束 三、编译运行命令大白话翻译1.编译内核模块 bash make-C/lib/modules/$(uname-r)/buildM$PWDmodules-调用Linux内核的编译工具-把C代码编译成内核能加载的.ko 模块文件2.加载内核模块 bash sudo insmod mydev.ko-把编译好的模块装进Linux内核-加载后系统就会出现/dev/mydev 这个设备3.运行PHPbash sudo php call.php-必须用root访问/dev 设备需要权限-PHP通过FFI和内核设备通信输出结果42四、整套逻辑极简总结1.内核模块造/dev/mydev 设备实现「数字×2」功能2.PHPFFI绕开PHP扩展直接调用系统函数和内核设备通信3.传参→内核计算→返回结果全程跨内核态/用户态完成4.miscdevice 简化了设备开发不用写复杂内核代码

更多文章