前言
在Linux中,如果要对特定的硬件端口进行操作,用户空间是没有足够的权限的,可以在内核模块中实现端口的读写操作,然后用户空间中的程序通过内核模块的ioctl进行操作,相关的代码实现和操作记录备忘。
内核模块代码
以下是一个简单的Linux内核驱动,可以实现对特定IO端口进行读写操作。这个驱动可以将一个整数写入端口,并从端口读取一个整数,并将结果返回给用户空间。具体实现如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <asm/io.h>
#define DRIVER_NAME "my_port_driver"
#define IOCTL_WRITE_PORT _IOW('k', 1, int)
#define IOCTL_READ_PORT _IOR('k', 2, int)
MODULE_LICENSE("GPL");
static int my_port_driver_open(struct inode *inode, struct file *file);
static int my_port_driver_release(struct inode *inode, struct file *file);
static long my_port_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static int port = 0x378;
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_port_driver_open,
.release = my_port_driver_release,
.unlocked_ioctl = my_port_driver_ioctl,
};
static dev_t dev;
static struct cdev my_cdev;
static int my_port_driver_init(void) {
int ret = alloc_chrdev_region(&dev, 0, 1, DRIVER_NAME);
if (ret) {
printk(KERN_ERR "Failed to allocate char device region\n");
return ret;
}
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_cdev, dev, 1);
if (ret) {
printk(KERN_ERR "Failed to add char device\n");
unregister_chrdev_region(dev, 1);
return ret;
}
printk(KERN_INFO "my_port_driver loaded\n");
return 0;
}
static void my_port_driver_exit(void) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "my_port_driver unloaded\n");
}
static int my_port_driver_open(struct inode *inode, struct file *file) {
return 0;
}
static int my_port_driver_release(struct inode *inode, struct file *file) {
return 0;
}
static long my_port_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int tmp;
switch (cmd) {
case IOCTL_WRITE_PORT:
if (copy_from_user(&tmp, (int *)arg, sizeof(int))) {
return -EFAULT;
}
outb(tmp, port);
break;
case IOCTL_READ_PORT:
tmp = inb(port);
if (copy_to_user((int *)arg, &tmp, sizeof(int))) {
return -EFAULT;
}
break;
default:
return -ENOTTY;
}
return 0;
}
module_init(my_port_driver_init);
module_exit(my_port_driver_exit);
这个驱动会注册一个字符设备,并且实现了open、release和ioctl三个函数。在用户空间,可以使用ioctl系统调用来调用驱动中的ioctl函数。这个驱动实现了两个命令:IOCTL_WRITE_PORT和IOCTL_READ_PORT。前者会将一个整数写入端口,后者会从端口读取一个整数。这个驱动代码中的inb和outb函数是内联汇编实现的,用于向端口发送和接收数据。inb函数会从指定端口读取一个字节,而outb函数会将一个字节写入指定端口。port变量表示要读写的IO端口的地址。需要注意的是,这个驱动没有进行足够的错误处理,例如没有检查用户空间指针是否合法。在实际的驱动开发中,需要进行更严格的错误处理。
如何使用
驱动编译时需要使用Linux内核源代码中提供的Makefile文件进行编译。具体编译步骤如下:
- 编写驱动代码并保存到文件中(例如my_port_driver.c)。
- 在Linux内核源代码目录中创建一个Kbuild文件,内容如下:
obj-m := my_port_driver.o
- 在Linux内核源代码目录中打开终端,执行以下命令进行编译:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
其中(pwd)表示当前目录。
4. 如果编译成功,会生成一个my_port_driver.ko文件。
加载驱动模块:
sudo insmod my_port_driver.ko
查看内核日志,确认驱动已经加载成功:
dmesg
可以使用ioctl系统调用进行读写操作。例如,向端口0x378写入数值100:
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define DEVICE_NAME "/dev/my_port_driver"
#define IOCTL_WRITE_PORT _IOW('k', 1, int)
int main() {
int fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
perror("Failed to open device file");
return -1;
}
int value = 100;
if (ioctl(fd, IOCTL_WRITE_PORT, &value) < 0) {
perror("Failed to write to port");
return -1;
}
close(fd);
return 0;
}
这个程序会向端口0x378写入数值100。
评论区