注册字符设备
首先应该定义两个结构体:1.字符设备结构体,2.对应文件操作的结构体
1.字符设备结构体如下:
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_alloc();
2.对应的文件操作的结构体:
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); //信号驱动
//......
};
struct cdev mydev;//定义自己的结构体
该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:
并且操作函数可以选,比如定义如下操作结构体:
struct file_operations my_ops = {//定义自己的文件操作对象
.owner = THIS_MODULE,
.open = mychar_open,//mychar_open需要自己按照上面结构体函数进行实现
.release = mychar_close//mychar_close需要自己按照上面结构体函数进行实现
};
上述两个结构定义完后,可以按照下面函数进行cdev设备的初始化:
void cdev_init(struct cdev *cdev,const struct file_operations *fops);
然后通过下面两行将设备添加进内核中数据结构中:
mydev.owner = THIS_MODULE;
cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
p:指向被添加的设备
dev:设备号
count:设备数量,一般填1
上述都是在加载模块即入口函数中操作的,当卸载模块时,在卸载函数中操作如下:
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
p:指向被移除的字符设备
此外最后还需要注销设备号
最后一个整体的驱动模块程序mychar.c如下所知:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
int major = 11;
int minor = 0;
int mychar_num = 1;
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 cdev mydev;
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, mychar_num);
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);
上述驱动代码编写好后,通过sudo mknod /dev/mydev c 主设备号 次设备号来创建一个设备文件
再编写一个应用程序tca来验证驱动代码功能正确:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = -1;
if(argc < 2)
{
printf("This argument is too few\n");
return 1;
}
fd = open(argv[1], O_RDONLY);
if(fd < 0)
{
printf("open %s failed\n", argv[1]);
return 2;
}
close(fd);
fd = -1;
return 0;
}
命令行中输入:./tca /dev/mydev
并通过dmesg查看内核打印信息为驱动模块中的mychar_open()定义的操作,以及关闭时mychar_close()定义的操作。
小结:
字符设备驱动开发步骤:
- 如果设备有自己的一些控制数据,则定义一个包含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命令查看内核打印信息
字符设备驱动与应用程序之间读写和ioctl操作
主要通过实现驱动模块中读写以及ioctl相应的函数,并在file_oprations中添加上实现的函数,然后编写应用程序进行测试。
驱动模块如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11;
int minor = 0;
int mychar_num = 1;
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
};
struct mychar_dev gmydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_dev, mydev);
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;
}
ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser, 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);
pmydev->curlen -= size;
return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(count > BUF_LEN-pmydev->curlen)
{
size = BUF_LEN-pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf+pmydev->curlen, puser, size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret, &maxlen, sizeof(int));
if(ret)
{
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
if(ret)
{
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The cmd is unknow\n");
return -1;
}
return 0;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.unlocked_ioctl = mychar_ioctl
};
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(&gmydev.mydev, &myops);
//将struct cdev对象添加到内核对应的数据结构里
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev, devno, mychar_num);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
测试的应用程序如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "mychar.h"
int main(int argc, const char *argv[])
{
int fd = -1;
char buf[8] = "";
int max = 0;
int cur = 0;
if(argc < 2)
{
printf("This argument is too few\n");
return 1;
}
fd = open(argv[1], O_RDWR);
if(fd < 0)
{
printf("open %s failed\n", argv[1]);
return 2;
}
ioctl(fd, MYCHAR_IOCTL_GET_MAXLEN, &max);
printf("max len is %d\n", max);
write(fd, "hello", 6);
ioctl(fd, MYCHAR_IOCTL_GET_CURLEN, &cur);
printf("cur len is %d\n", cur);
read(fd, buf, 8);
printf("buf=%s\n", buf);
close(fd);
fd = -1;
return 0;
}
头文件mychar.h如下:
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC, 1, int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC, 2, int*)
#endif