文章目录
本文和大家主要分享的是对前期的字符设备驱动进行一个小小的整合,主要的特点就是对字符设备的关键内容和属性进行结构体整合,这样使得代码更具有可移植性和可读性!这也是作为程序员良好编程风格的体现。
字符设备驱动代码优化
大家需要仔细阅读以下代码,都是经过我认真备注的,其中值得注意的是:
container_of的作用:已知结构体成员的地址,获取结构体的地址
container_of(某一个成员的地址, 结构体类型, 结构体成员名)
其中ptr和member必须是结构体中同一类型的
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
计算方法:(char *)(ptr) - offsetof(type, member)
pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_dev, mydev);
已知i_cdev的地址,在struct mychar_dev中查找mydev的地址,将设备地址赋值给pfile->private_data
希望大家能把这个用法掌握!
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define BUF_LEN 100
int major = 11; //主设备号
int minor = 0; //次设备号
int mychar_num = 1; //设备数量
struct mychar_dev
{
struct cdev mydev; //每一类设备都有一个cdev结构体
char mydev_buf[BUF_LEN]; //内核空间
int curlen; //有效数字从零开始
};
struct mychar_dev gmydev;
这里将内核空间定义进设备结构体内部,更具有面向对象的思想,并且重新定义全局结构体struct mychar_dev gmydev,后续操作的结构体其实还是该结构体中的gmydev.mydev这个结构体
int mychar_open(struct inode *pnode, struct file *pfile)
{
//container_of的作用:已知结构体成员的地址,获取结构体的地址
//container_of(某一个成员的地址, 结构体类型, 结构体成员名)
/* 其中ptr和member必须是结构体中同一类型的
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
*/
//计算方法:(char *)(ptr) - offsetof(type, member)
pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_dev, mydev);
//已知i_cdev的地址,在struct mychar_dev中查找mydev的地址,将设备地址赋值给pfile->private_data
printk("mychar open is called!!!\n");
return 0;
}
ssize_t mychar_read(struct file *pfile, char __user *pbuf, size_t count, loff_t *ppos)
{
int size = 0;
int ret = 0;
//在open函数中已经对pfile->private_data进行了赋值
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if (count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf, pmydev->mydev_buf, size);
if (ret)
{
printk("copy_to_user failed!\n");
return -1;
}
memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size); //把在mydev_buf中剩下有效数据存放在以mydev_buf的首地址中
pmydev->curlen -= size; //读走的字节要被减去
return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *pbuf, size_t count, loff_t *ppos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if (count < BUF_LEN - pmydev->curlen)
{
size = count;
}
else
{
size = BUF_LEN - pmydev->curlen;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, pbuf, size);
if (ret)
{
printk("copy_from_user failed!\n");
return -1;
}
pmydev->curlen += size;
return size;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar clsoe is called!!!\n");
return 0;
}
随着对结构体进行封装,那么此时对于mychar_read和mychar_write中使用的比如curlen等变量已经封装进了结构体中,因此需要使用结构体变量对其进行替换,替换结果也如上述代码所示,需要注意的是,在mychar_open函数中,对struct file *pfile结构体中的pfile->private_data进行赋值,传递私有变量指向定义的全局结构体;
/* 对字符设备的操作函数 */
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.write = mychar_write,
.read = mychar_read,
.release = mychar_close,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor); //组合设备号
ret = register_chrdev_region(devno, mychar_num, "mychar"); //手动申请设备号
if (ret) //返回值为0表示申请成功
{
ret = alloc_chrdev_region(&devno, 0, mychar_num, "mychar"); //申请失败则系统自动分配
if (ret)
{
printk("get devno failed!\n");
return -1;
}
major = MAJOR(devno); //从系统分配的设备号中取出主设备号
minor = MINOR(devno); //从系统分配的设备号中取出次设备号
devno = MKDEV(major, minor); //组合设备号
}
/* 使得设备具有myops中的函数操作方法 */
cdev_init(&gmydev.mydev, &myops);
gmydev.mydev.owner = THIS_MODULE;
/* 将设备号为devno的这个设备(mydev)添加到内核(内核hash链表中) */
cdev_add(&gmydev.mydev, devno, mychar_num);
printk("hello world!\n");
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor); //组合设备号
unregister_chrdev_region(devno, mychar_num); //注销设备号
/* 从内核中删除mydev这个设备 */
cdev_del(&gmydev.mydev);
printk("bye bye!!!\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
需要注意的是,有的小伙伴可能有疑惑,为什么获取的是
pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_dev, mydev)
明明是指向我们定义结构体,而且mychar_read和mychar_write函数都操作的是这个结构体;但是我们定义的结构体实体明明是struct mychar_dev gmydev;
那么其本质就是我们的确实定义了struct mychar_dev gmydev这个实体,并且我们使用gmydev.mydev将其加入内核的设备链表中,通过struct inode *pnode可以找到它,因此就有了在mychar_open函数中使用container_of函数,结合已经知道的pnode->i_cdev设备节点地址就能够找到定义的结构体,这是因为当我们定义struct mychar_dev结构体是,其成员mydev的地址就已经固定。我们所做的操作就是不停的寻找操作目标,通过pnode->i_cdev寻找包含该成员的结构体以此操作其他成员变量。
应用层代码无需更改!!!
总结
希望大家能够认真阅读我个人的浅显理解,如果能理解我相信大家都会恍然大悟!最后,各位小伙伴们如果有收获,可以点赞收藏哦,你们的认可是我创作的动力,一起加油!