完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
驱动入门:一个简单的字符设备驱动 首先我要向大家推荐一下韦东山老师的视频,他在视频里讲解的非常的好,把代码分析的非常的透彻,而且他会在视频里现场写出每一个程序的代码,而不是从其他的地方拿一个程序过来分析就完事,所以我们可以跟着他一步一步的学习linux程序设计。 我就按照这几天在视频里从韦老师那儿学到的方法,讲一下写简单字符设备的流程,以在书上看到的globalmem这样的一个虚拟设备为例。这个设备的功能是在内核空间里分配4K字节的内存,在驱动中提供如何访问和操作这块内存的函数,以供用户空间的进程通过系统调用来访问这块内存。我们可以把它看成是最大容量为4K的普通文件。我们要能够像打开普通文件一样打开它,读取其中的内容,或向其中写入数据,定位到文件的某个位置处,清空其中的内容,然后关闭打开的文件。我们的应用程序只需要通过系统调用open()、read()、write()、lseek()、ioctl()、release()。而不用去管它是一个普通文件还是一个字符设备。 第一步、包含进文件中所需的头文件,和宏定义,声明全局变量,并定义一个设备结构体。 #include #include #include #include #include #include #include #include #include #include #include #define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/ #define MEM_CLEAR 0x1 /*清0全局内存*/ #define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/ static int globalmem_major = GLOBALMEM_MAJOR; /*globalmem设备结构体*/ struct globalmem_dev { struct cdev cdev; /*cdev结构体*/ unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/ }; 第二步、写出驱动框架。 static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .llseek = globalmem_llseek, .read = globalmem_read, .write = globalmem_write, .ioctl = globalmem_ioctl, .open = globalmem_open, .release = globalmem_release, }; file_operations结构体里面有很多的函数,但并非要实现其中所有的成员函数。要根据实际的需要向file_operations里添加成员函数,这里实现6个函数。 第三步、分别实现file_operations里的每个函数。 /*文件打开函数*/ int globalmem_open(struct inode *inode, struct file *filp) { return 0; /*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/ } /*文件释放函数*/ int globalmem_release(struct inode *inode, struct file *filp) { return 0; /*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/ } /*读函数,读取其中的内容并返回读取的大小*/ static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; /*当前指针所在的位置*/ unsigned int count = size; /*要读取的大小*/ int ret = 0; /*分析和获取有效的写长度*/ if (p >= GLOBALMEM_SIZE) /*如果当前指针已经到设备最尾端*/ return count ? - ENXIO: 0; /*要读取的大小不为0,则返回出错信息*/ if (count > GLOBALMEM_SIZE - p) /*如果还有可读取的数据不够conut大小*/ count = GLOBALMEM_SIZE - p; /*返回conut个数据*/ /*内核空间->用户空间*/ if (copy_to_user(buf, (void*)(dev->mem + p), count)) /*用户的地址空间和内核空间的不能直接传输数据,必须通过copy_to_user copy_from_user来在两个地址空间中传递数据 */ { ret = - EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %d bytes(s) from %dn", count, p); } return ret; } /*写函数*/ static ssize_t globalmem_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; /*分析和获取有效的写长度*/ if (p >= GLOBALMEM_SIZE) /*没有可写的空间了*/ return count ? - ENXIO: 0; /*如果要写非零个数据,则返回出错信息*/ if (count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; /*用户空间->内核空间*/ if (copy_from_user(dev->mem + p, buf, count)) ret = - EFAULT; else { *ppos += count; ret = count; printk(KERN_INFO "written %d bytes(s) from %dn", count, p); } return ret; } /* seek文件定位函数 */ static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch (orig) { case 0: /*相对文件开始位置偏移*/ if (offset < 0) { ret = - EINVAL; break; } if ((unsigned int)offset > GLOBALMEM_SIZE) { ret = - EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: /*相对文件当前位置偏移*/ if ((filp->f_pos + offset) > GLOBALMEM_SIZE) { ret = - EINVAL; break; } if ((filp->f_pos + offset) < 0) { ret = - EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } /* ioctl设备控制函数 ,这里只是实现一个清空该内存的命令*/ static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case MEM_CLEAR: memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zeron"); break; default: return - EINVAL; } return 0; } 第四步、实现注册和卸载函数 到这里我们看似已经实现设备的功能了,但是应用程序还不能够使用它,因为还没有注册该设备,所以我们要把这个设备加载进内核,相对应的当我们不需要该设备了的时候就应该把它从内核卸载掉。在模块加载函数中完成的功能主要有:申请设备号、注册cdev设备结构体。相应的卸载函数中就应该卸载cdev设备结构体,释放设备号。 /*初始化并注册cdev*/ static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(globalmem_major, index); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &globalmem_fops; err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding LED%d", err, index); } /*设备驱动模块加载函数*/ int globalmem_init(void) { int result; dev_t devno = MKDEV(globalmem_major, 0); /* 申请设备号*/ if (globalmem_major) result = register_chrdev_region(devno, 1, "globalmem"); else /* 动态申请设备号 */ { result = alloc_chrdev_region(&devno, 0, 1, "globalmem"); globalmem_major = MAJOR(devno); } if (result < 0) return result; /* 动态申请设备结构体的内存*/ globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL); if (!globalmem_devp) /*申请失败*/ { result = - ENOMEM; goto fail_malloc; } memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); globalmem_setup_cdev(globalmem_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ void globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); /*注销cdev*/ kfree(globalmem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/ } 第五步、声明一些必要的信息。 我们看看前面的加载和卸载函数,它们与另外的几个函数并没有什么特别之处,当我们加载驱动的时候,内核怎么知道要调用globalmem_init()函数,在卸载驱动时怎么知道调用globalmem_exit()呢?所以我们应该向内核指示它们就是入口和出口函数,这就宏module_init()和module_exit()的作用。 module_init(globalmem_init); module_exit(globalmem_exit); 除此之外我们还必须声明我们的驱动遵循的license,不然会报错。 MODULE_LICENSE("Dual BSD/GPL"); 到这里我们的这个简单的设备就写好了。 |
|
相关推荐
|
|
唉!我就一个学嵌入式的,学的ARM7,数电,模电,LINUX,就是找不到对口的嵌入式工作,在学校学的也算很努力了,天天加班看书做实验,写代码,最后居然没人要,要我的就2K到3K,现在都不知道还要不要在去坚持,都快毕业了,花了我三年去努力的学,最后尽比不上民工,想想有点后悔,当初大学四年还不如去混,去玩
|
|
|
|
|
|
|
|
|
|
787 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-mfgtools烧录流程介绍之烧写所需镜像
888 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-mfgtools烧录流程之烧写方法
608 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-内核编译之初次编译
905 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-内核源代码的目录结构和文件说明
821 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 20:33 , Processed in 0.745466 second(s), Total 64, Slave 55 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号