Linux字符设备驱动程序开发(1)-使用字符设备驱动

Linux字符设备驱动程序开发(1)-使用字符设备驱动1 使用字符设备驱动程序 1 1 编译 安装驱动在 Linux 系统中 驱动程序通常采用内核模块的程序结构来进行编码 因此 编译 安装一个驱动程序 其实质就是编译 安装一个内核模块 把下面的范例代码拷贝到 Linux 系统中 include linux module h include linux fs h include linux linux

1、使用字符设备驱动程序

1.1编译/安装驱动

在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。把下面的范例代码拷贝到Linux系统中:

#include 
  
    #include 
   
     #include 
    
      #include 
     
       #include 
      
        int dev1_registers[5]; int dev2_registers[5]; struct cdev cdev; dev_t devno; /*文件打开函数*/ int mem_open(struct inode *inode, struct file *filp) { /*获取次设备号*/ int num = MINOR(inode->i_rdev); if (num==0) filp->private_data = dev1_registers; else if(num == 1) filp->private_data = dev2_registers; else return -ENODEV; //无效的次设备号 return 0; } /*文件释放函数*/ int mem_release(struct inode *inode, struct file *filp) { return 0; } /*读函数*/ static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/ /*判断读位置是否有效*/ if (p >= 5*sizeof(int)) return 0; if (count > 5*sizeof(int) - p) count = 5*sizeof(int) - p; /*读数据到用户空间*/ if (copy_to_user(buf, register_addr+p, count)) { ret = -EFAULT; } else { *ppos += count; ret = count; } return ret; } /*写函数*/ static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; int *register_addr = filp->private_data; /*获取设备的寄存器地址*/ /*分析和获取有效的写长度*/ if (p >= 5*sizeof(int)) return 0; if (count > 5*sizeof(int) - p) count = 5*sizeof(int) - p; /*从用户空间写入数据*/ if (copy_from_user(register_addr + p, buf, count)) ret = -EFAULT; else { *ppos += count; ret = count; } return ret; } /* seek文件定位函数 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case SEEK_SET: newpos = offset; break; case SEEK_CUR: newpos = filp->f_pos + offset; break; case SEEK_END: newpos = 5*sizeof(int)-1 + offset; break; default: return -EINVAL; } if ((newpos<0) || (newpos>5*sizeof(int))) return -EINVAL; filp->f_pos = newpos; return newpos; } /*文件操作结构体*/ static const struct file_operations mem_fops = { .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, }; /*设备驱动模块加载函数*/ static int memdev_init(void) { /*初始化cdev结构*/ cdev_init(&cdev, &mem_fops); /* 注册字符设备 */ alloc_chrdev_region(&devno, 0, 2, "memdev"); cdev_add(&cdev, devno, 2); } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(&cdev); /*注销设备*/ unregister_chrdev_region(devno, 2); /*释放设备号*/ } MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit); 
       
      
     
    
  
obj-m := memdev.o KDIR := /home/S5-driver/lesson7/linux-tiny6410/ all: make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order 

#make

 

拷贝memdev.ko到开发板中,然后安装:

#insmod memdev.ko

1.2创建设备文件

Linux字符设备驱动程序开发(1)-使用字符设备驱动

由这幅图可以知道,应用程序对设备驱动的访问是通过设备文件来进行的,设备文件就像一个媒介一样把应用程序和设备驱动联系在了一起。

它的参数有4个,第一个是需要创建的字符设备的文件名字,小C代表字符设备文件,主设备号用来和设备驱动程序建立连接,它们需要使用相同的主设备号,那么怎么查看刚刚安装的mendev驱动程序使用的设备号呢?在开发板上输入:

#cat /proc/device

显示出了2列信息,第一列是主设备号,第二列是设备驱动程序的名字。这里应该是253

 

次设备号取非负的数即可,一般为0~255之间。

 

现在我们在开发板上创建一个设备文件(设备文件名称不和已有的冲突即可):

#mknod /dev/memdev0 c 253 1

然后查看dev:

#ls /dev

1.3访问设备

这个设备文件其实就是对内存的操作,因为在实际的硬件设备访问中,最终都是对设备寄存器的访问,这个访问内存中的某几个单元达到的效果是一样的。

编写一个应用程序来访问这个设备文件:

#include 
  
    #include 
   
     #include 
    
      #include 
     
       int main() { int fd; int src=1234; int tmp=0; fd = open("/dev/memdev0",O_RDWR); write(fd, &src, sizeof(int)); printf("src:%d\n",src); close(fd); fd = open("/dev/memdev0",O_RDWR); read(fd, &tmp, sizeof(int)); printf("tmp:%d\n",tmp); close(fd); return 0; } 
      
     
    
   int fd; int src=1234; int tmp=0; fd = open("/dev/memdev0",O_RDWR); write(fd, &src, sizeof(int)); printf("src:%d\n",src); close(fd); fd = open("/dev/memdev0",O_RDWR); read(fd, &tmp, sizeof(int)); printf("tmp:%d\n",tmp); close(fd); return 0; } 

使用arm-linux-gcc编译后把生成文件拷贝到开发板中,然后执行,如果发现错误:

~bin/sh: ./write_mem not found

其实这并不是找不到write_mem这个文件,而是它依赖的库找不到,可以使用:

#arm-readelf  -d  write_mem

-d表示查询动态连接库,来查看它所依赖的库

 

这个时候方法有2钟,1是拷贝它依赖的库到开发板的lib目录中,2是在编译的时候使用静态连接:

#arm-linux-gcc  -static  write_mem.c  -o write_mem

 

再编译运行,可以看到读写都已经成功了,注意写完后一定要关闭文件才把数据真正写入了,如果没有关闭直接读,读出来的是空的。!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/176772.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午9:05
下一篇 2026年3月26日 下午9:05


相关推荐

  • Kimi Code 已接入 K2.5,所有用户限时3倍使用额度

    Kimi Code 已接入 K2.5,所有用户限时3倍使用额度

    2026年3月12日
    3
  • Linux【实操篇】—— 远程登录、远程文件传输、vi和vim工具的使用方法

    Linux【实操篇】—— 远程登录、远程文件传输、vi和vim工具的使用方法本文详细介绍了在本地远程登录到 Linux 系统端的操作方法以及在 Windows 与 Linux 两端之间进行文件传输的方法 还有 vi 和 vim 相关快捷键的使用

    2025年8月2日
    7
  • gear s3刷android wear,【干货】三星Gear S3/Gear S3 classic 智能手表刷机教程「建议收藏」

    gear s3刷android wear,【干货】三星Gear S3/Gear S3 classic 智能手表刷机教程「建议收藏」今年九月在IFA电子展正式亮相的三星GearS3在本月即将在国内开卖,目前不少电商的商家已经为这款新品进行预热,从电商的价格来看这两款定位不同的智能手表(经典款/先锋款)的价格都为3599元,三星GearS3支持IP68级别防水,兼容Android4.4以及之后的安卓系统版本以及运存1.5GB以上的安卓智能手机。与苹果AppleWatch有诊断接口但是无法刷机不同,三星GearS系列手表支…

    2022年7月21日
    66
  • mknod详解

    mknod详解br br mknod makeblockorc mknod OPTION NAMETYPE MAJORMINOR br nbsp nbsp nbsp option 有用的就是 m 了 br nbsp nbsp nbsp name nbsp nbsp 自定义 br nbsp nbsp nbsp type nbsp nbsp 有 b 和 c 还有 pbr nbsp nbsp nbsp 主设备号 br nbsp nbsp nbsp 次设备号 br br 主设备号是由 usr src linux i

    2026年3月17日
    1
  • 周鸿祎谈使用龙虾有哪些安全问题

    周鸿祎谈使用龙虾有哪些安全问题

    2026年3月14日
    4
  • ArcGIS地图打印

    ArcGIS地图打印个人学习笔记 仅供学习交流 参考书籍 ArcGIS 从 0 到 1 文章目录布局编辑插入 Excel 的方法插入图片固定比例尺打印导出地图局部打印批量打印标准分幅打印一张图多比例尺打印布局编辑 ArcGIS 的地图打印是在布局视图中完成的 所以地图打印前一定要切换到布局视图 切换方法 点击左下角的按钮 右键数据框可以设置数据框的属性 大小和位置等等 在主菜单 文件 下拉菜单下 选择 页面和打印设置 可以更改打印方式 插入 Excel 的方法 打开 excel 文档 选择需要打印的表格数据

    2026年3月19日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号