一个简单的linux内核驱动

    科技2025-10-10  16

    一,内核结构简单概述 上层程序操作设备驱动简单概述: 在用户空间使用相关的c库,比如open函数会造成一个中断,系统会去调用sys_call函数(系统调用),然后会去调用相关的sys_open函数,在内核空间的时候会去驱动链表里面查找对应的外设驱动,我们编写完驱动程序,加载到内核,内核会去调用相关的驱动open函数 二,字符设备驱动简单概述 字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备、其驱动程序中完成的主要工作是初始化、添加和删除 struct cdev 结构体,申请和释放设备号,以及填充 struct file_operations 结构体中断的操作函数,实现 struct file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。

    如图,在Linux内核代码中:

    使用struct cdev结构体来抽象一个字符设备; 通过一个dev_t类型的设备号(分为主(major)、次设备号(minor))一确定字符设备唯一性; 通过struct file_operations类型的操作方法集来定义字符设备提供个VFS的接口函数。

    三,编写驱动程序代码 注:一个简单的驱动程序框架

    #include <linux/fs.h>//file_operation所在的头文件 #include <linux/module.h>//module_init mmodule_exit声明 #include <linux/init.h>//__init __exit宏定义申明 #include <linux/device.h>//class device声明 #include <linux/uaccess.h>//copy_from_user的头文件 #include <linux/types.h>//设备号dev_t的声明 #include <asm/io.h>//ioremap iounmap的头文件 static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno;//设备号 static int major = 231;//主设备号 static int minor = 0;//次设备号 static char *module_name = "pin4";//模块名 //open函数 static int pin4_open(struct inode *inode,struct file *file)//函数模型都在file operations结构体里面 { printk("hello I am open!\n");//内核打印函数,和c库中printf类似 return 0; } //write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos) { printk("hello I am write!\n"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open = pin4_open, .write = pin4_write, }; int __init pin4_drv_init(void)//真实驱动入口 { int ret; devno = MKDEV(major,minor);//创建设备号 ret = register_chrdev(major,module_name,&pin4_fops);//注册驱动,告诉内核,把这个驱动加入到内核的链表中 pin4_class = class_create(THIS_MODULE,"myfirstdemo");//让代码自动生成设备 pin4_class_dev = device_create(pin4_class,NULL,devno,NULL,module_name);//创建设备文件 return 0; } void __exit pin4_drv_exit(void)//卸载驱动 { device_destroy(pin4_class,devno);//销毁设备号 class_destroy(pin4_class);//销毁类 unregister_chrdev(major,module_name);//卸载驱动 } module_init(pin4_drv_init);//内核加载驱动时,这个宏会被调用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");

    四,编译驱动代码 1,进入内核源码字符设备驱动里面

    cd /home/user/Linux-Kernal/linux-rpi-4.14.y/drivers/char

    2,拷贝编写的驱动代码到当前目录

    cp /home/user/hello.c ./

    3,修改字符设备里面的Makefile 在前面加上

    obj-m +=hello.o//驱动代码文件名.o

    4,回到linux内核源码目录

    cd ../../

    5,编译驱动为一个模块

    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 注意:/*内核架构 交叉编译工具 内核版本*/

    注:zsh编译时会报错,主要是对环境变量配置不成功,一般默认时bash,正常配置交叉编译工具时候并不会报错 五,加载内核驱动 1,将.ko文件拿到树莓派并且加载内核驱动

    scp /drivers/char/hello.ko pi@192.168.0.0:/home/pi

    2,在树莓派加载.ko文件到设备驱动

    sudo insmod hello.ko

    3,查看设备驱动是否被成功加载

    ls /dev/pin4

    4,修改设备文件权限,让他可以打开和读写权限对于普通用户

    sudo chmod 666 /dev/pin4

    六,使用c库操作新的内核驱动 1,简单代码 注:只实现了对驱动代码的简单验证,由于在编写驱动代码时候没有编写相关业务,所以不能像别的io口驱动一样去使用

    #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd; fd = open("/dev/pin4",O_RDWR); if(fd < 0) { printf("dev open error\n"); }else{ printf("dev open sucess\n"); } write(fd,'1',1); return 0; }

    2,在树莓派上运行编译的验证代码 结果如下: 在终端会打印dev open sucess 3,查看printk打印的内容 注: 如果有以上结果表示内核加载和编译执行并且操作成功! 每次树莓派开关机都要重新加载驱动才可以使用

    Processed: 0.017, SQL: 8