misc 混杂设备
混杂设备,本质上还是字符设备。其主设备号固定为 10,次设备号作为各个混杂设备的区分的依据。
minor 一般使用 MISC_DYNAMIC_MINOR 即可。
name 即设备文件,也即 /dev/ 目录下的 hello 文件。
fops 与前文的 fops 意义相同。
misc 对象的定义与初始化如下:
注册以及卸载混杂设备。
注册时,无需手动mknod 创建设备文件,注册API 会同时创建对应的name域的文件。
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // struct file_operations
#include <linux/cdev.h> // struct cdev
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/string.h>
#include <linux/miscdevice.h>
static int hello_open(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===\n",__FILE__, __func__);
return 0;
}
static int hello_close(struct inode *inode,
struct file *file)
{
printk("===[frocheng]===[%s]===[%s]===\n",__FILE__, __func__);
return 0;
}
// 定义初始化LED的硬件操作对象
// open,release一旦加载内存中,静静等待着应用程序来调用
static struct file_operations hello_fops =
{
.owner = THIS_MODULE,
.open = hello_open, // 打开设备
.release = hello_close // 关闭设备
};
static const char name[] = "hello";
// 定义初始化混杂设备对象
static struct miscdevice hello_misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = name,
.fops = &hello_fops
};
static int __init hello_init(void)
{
int rc = -1;
printk("===[frocheng]===[%s]===[%s]===[Hello !]===\n",__FILE__, __func__);
rc = misc_register(&hello_misc); // 注册
if (rc != 0)
{
printk("===[frocheng]===[%s]===[%s]===[misc_register error with %d]===\n",__FILE__, __func__, rc);
return -1;
}
printk("===[frocheng]===[%s]===[%s]===[name=%s, nodename = %s]===\n",
__FILE__, __func__, name, hello_misc.nodename);
return 0;
}
static void __exit hello_exit(void)
{
misc_deregister(&hello_misc); // 卸载
printk("===[frocheng]===[%s]===[%s]===[Bye bye...]===\n",__FILE__, __func__);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");
ioctl 系统调用向设备发送命令
ioctl 的系统调用原型如上
功能
该系统调用,可以向设备发送命令,进行控制;另可进行设备的读写操作。
参数
fd:设备文件
request: 请求类型,驱动层,如果支持该系统调用操作,需要预先定义该 request的具体意义
后面是可变参数,而到内核底层,会转化成一个 unsigned long。
这种方式,也很操蛋。如果真的需要,我们可以定义数据结构,用地址往下传,然后unsigned long拿到之后,转回结构地址。
返回值
成功,我们返回0即可。
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // struct file_operations
#include <linux/cdev.h> // struct cdev
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/string.h>
#include <linux/miscdevice.h>
#define HELLO_OPEN (0U)
#define HELLO_CLOSE (1U)
long hello_unlocked_ioctl(struct file * f, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case HELLO_OPEN:
{
printk("===[frocheng]===[%s]===[%s]===[open]===\n",__FILE__, __func__);
}
break;
case HELLO_CLOSE:
{
printk("===[frocheng]===[%s]===[%s]===[close]===\n",__FILE__, __func__);
}
break;
default:
{
printk("===[frocheng]===[%s]===[%s]===[Unkown Command]===\n",__FILE__, __func__);
}
return -1;
}
return 0;
}
// 定义初始化LED的硬件操作对象
static struct file_operations hello_fops =
{
.owner = THIS_MODULE,
.unlocked_ioctl = hello_unlocked_ioctl
};
static const char name[] = "hello";
// 定义初始化混杂设备对象
static struct miscdevice hello_misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = name,
.fops = &hello_fops
};
static int __init hello_init(void)
{
int rc = -1;
printk("===[frocheng]===[%s]===[%s]===[Hello !]===\n",__FILE__, __func__);
rc = misc_register(&hello_misc); // 注册
if (rc != 0)
{
printk("===[frocheng]===[%s]===[%s]===[misc_register error with %d]===\n",__FILE__, __func__, rc);
return -1;
}
printk("===[frocheng]===[%s]===[%s]===[name=%s, nodename = %s]===\n",
__FILE__, __func__, name, hello_misc.nodename);
return 0;
}
static void __exit hello_exit(void)
{
misc_deregister(&hello_misc); // 卸载
printk("===[frocheng]===[%s]===[%s]===[Bye bye...]===\n",__FILE__, __func__);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");
测试代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#define HELLO_OPEN (0UL)
#define HELLO_CLOSE (1UL)
int main()
{
int fd = open("/dev/hello", O_RDWR);
if (fd < 0)
{
perror("open");
return 0;
}
do
{
int ret = ioctl(fd, HELLO_OPEN);
if (ret != 0)
{
perror("ioctl");
break;
}
printf("open success!\n");
sleep(1);
ret = ioctl(fd, HELLO_CLOSE);
if (ret != 0)
{
perror("ioctl");
break;
}
printf("close success!\n");
sleep(1);
ret = ioctl(fd, 234);
if (ret != 0)
{
perror("ioctl");
printf("Other command error!\n");
break;
}
} while(0);
close(fd);
return 0;
}
结果
注意,先加载后ioctl_test,后查看dmesg,最后不要忘记卸载。
整型原子操作,设备只能被一个所有者打开
示例代码
主要依赖于原子操作接口
atomic_dec_and_test: 该接口
1. 将入参的atomic_t 类型参数,-1
2. 返回之后-1的数与0的结果,如果相等,返回 true,如果不等,返回 false。
以上两部操作,在一个接口内完成,具有原子性。
另:
该段示例代码,利用 内核信号量,保证了读写操作的原子性,并且,没有遵循Linux 文件偏移量的规则,内部自行维护了一个一个空间,内部自行保证该空间内部读写的安全性。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // struct file_operations
#include <linux/cdev.h> // struct cdev
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/string.h>
static struct class *cls;
#define HELLO_MAX_SIZE (32)
static DEFINE_SEMAPHORE(sema);
static char bf[HELLO_MAX_SIZE];
static size_t sz;
static char* name = "hello";
static atomic_t g_open_cnt = ATOMIC_INIT(1);
static int hello_open(struct inode *inode,
struct file *file)
{
if (!atomic_dec_and_test(&g_open_cnt))
{
atomic_inc(&g_open_cnt);
return -EBUSY; // 返回设备忙错误
}
printk("===[frocheng]===[%s]===[%s]===\n",__FILE__, __func__);
return 0;
}
static int hello_close(struct inode *inode,
struct file *file)
{
atomic_inc(&g_open_cnt);
printk("===[frocheng]===[%s]===[%s]===\n",__FILE__, __func__);
return 0;
}
static ssize_t hello_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
size_t cnt = 0;
if (count <= 0 || file == NULL || ppos == NULL || buf == NULL)
{
return -EINVAL;
}
down(&sema);
if (sz <= 0)
{
up(&sema);
return 0;
}
if (sz <= count)
{
cnt = sz;
}
else
{
cnt = count;
}
copy_to_user(buf, bf, cnt);
memmove(bf, bf + cnt, sz - cnt);
sz -= cnt;
// *ppos += cnt;
up(&sema);
return cnt; // 返回实际读取的字节数
}
static ssize_t hello_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
if (count <= 0 || file == NULL || ppos == NULL || buf == NULL)
{
return -EINVAL;
}
down(&sema);
if (count + sz > HELLO_MAX_SIZE)
{
up(&sema);
return -EAGAIN;
}
copy_from_user(bf + sz, buf, count);
sz += count;
// *ppos += count;
up(&sema);
return count;
}
// 定义初始化LED的硬件操作对象
// open,release一旦加载内存中,静静等待着应用程序来调用
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open, // 打开设备
.release = hello_close, // 关闭设备
.write = hello_write,
.read = hello_read
};
// 定义字符设备对象
static struct cdev hello_cdev;
// 定义设备号对象
static dev_t dev;
static int __init hello_init(void)
{
int rc = -1;
printk("===[frocheng]===[%s]===[%s]===[Hello !]===\n",__FILE__, __func__);
// 申请设备号
rc = alloc_chrdev_region(&dev, 0, 1, name);
if (rc != 0)
{
printk("===[frocheng]===[%s]===[%s]===[alloc_chrdev_region error with %d]===\n",__FILE__, __func__, rc);
// hello 驱动模块加载失败,因为并没有申请到字符神资源,驱动加载失败。
return -1;
}
// 自动创建 device
cls = class_create(THIS_MODULE, name);
device_create(cls, NULL, dev, NULL, name);
// 初始化字符设备对象
cdev_init(&hello_cdev, &hello_fops);
// 注册字符设备对象
cdev_add(&hello_cdev, dev, 1);
sema_init(&sema, 1);
printk("===[frocheng]===[%s]===[%s]===[name=%s, major=%u, minor=%u]===\n",__FILE__, __func__, name, MAJOR(dev), MINOR(dev));
return 0;
}
static void __exit hello_exit(void)
{
// 删除设备文件
device_destroy(cls, dev);
// 删除设备类
class_destroy(cls);
// 卸载字符设备对象
cdev_del(&hello_cdev);
// 释放设备号
unregister_chrdev_region(dev, 1);
printk("===[frocheng]===[%s]===[%s]===[Bye bye...]===\n",__FILE__, __func__);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");
MODULE_VERSION("V0.0.1");