五、注册字符设备
struct cdev
{
struct kobject kobj;//表示该类型实体是一种内核对象
struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
struct list_head list;//链表指针域
dev_t dev;//设备号
unsigned int count;//设备数量
};
自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:
直接定义:定义结构体全局变量
动态申请:
struct cdev * cdev_alloc()
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
struct file_operations
{
struct module *owner; //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
int (*open) (struct inode *, struct file *); //打开设备
int (*release) (struct inode *, struct file *); //关闭设备
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
loff_t (*llseek) (struct file *, loff_t, int); //定位
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
unsigned int (*poll) (struct file *, struct poll_table_struct *); //POLL机制,实现多路复用的支持
int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
int (*fasync) (int, struct file *, int); //信号驱动
//…
};
该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:
syscall与drvfunc
一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化
int cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
p:指向被添加的设备
dev:设备号
count:设备数量,一般填1
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
p:指向被移除的字符设备
小结:
字符设备驱动开发步骤:
如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
定义三个全局变量分别来表示主设备号、次设备号、设备数
定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE e. 添加字符设备到内核
module exit函数:a. 注销设备号 b. 从内核中移除struct cdev c. 如果如果是全局设备指针则释放其指向空间
编写各个操作函数并将函数名初始化给struct file_operations结构体变量
验证操作步骤:
编写驱动代码mychar.c
make生成ko文件
insmod内核模块
查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字
创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号
编写app验证驱动(testmychar_app.c)
编译运行app,dmesg命令查看内核打印信息
驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
int major=11;
int minor=0;
int mychar_num=1;
struct cdev mydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
struct file_operations myops={
.owner=THIS_MODULE,
.open=mychar_open,
.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)
{
ret=alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major=MAJOR(devno);
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&mydev,&myops);
/*将struct cdev对象添加到内核的数据结构中*/
mydev.owner=THIS_MODULE;
cdev_add(&mydev,devno,1);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno=MKDEV(major,minor);
cdev_del(&mydev);
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
app代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd =-1;
if(argc<2)
{
printf("argument is too few.\n");
}
fd=open(argv[1],O_RDONLY);
if(fd<0)
{
printf("open %s failed.\n",argv[1]);
return -1;
}
close(fd);
fd=-1;
return 0;
}
./testmychar_app /dev/mydev
app->运行设备->自动调用驱动代码->打印信息