目录
1 FIFO与虚拟设备
字符硬件设备内部有一个缓冲区(buffer),芯片内部提供了寄存器来访问这些FIFO,可以通过读寄存器把FIFO的内容读取出来,或者通过写寄存器把数据写入FIFO,或者通过寄存器写入FIFO,一般外设芯片支持中断模式,FIFO有数据到达时,外设芯片通过中断线告诉CPU。
下面做的实验就是模拟设备从用户空间写入内核空间FIFO,再从内核空间FIFO读数据到用户层读缓存区的。
2 编写虚拟设备驱动
2.1 简单的虚拟设备驱动
虚拟驱动代码如下:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/slab.h>
#define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
/*virtual FIFO device's buffer*/
static char *device_buffer;
#define MAX_DEVICE_BUFFER_SIZE 64
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t lbuf, loff_t *ppos)
{
int actual_readed;
int max_free;
int need_read;
int ret;
printk("%s enter\n", __func__);
max_free = MAX_DEVICE_BUFFER_SIZE - *ppos;
need_read = max_free > lbuf ? lbuf : max_free;
if (need_read == 0)
dev_warn(mydemodrv_device, "no space for read");
ret = copy_to_user(buf, device_buffer + *ppos, need_read);
if (ret == need_read)
return -EFAULT;
actual_readed = need_read - ret;
*ppos += actual_readed;
printk("%s, actual_readed=%d, pos=%d\n",__func__, actual_readed, *ppos);
return actual_readed;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int actual_write;
int free;
int need_write;
int ret;
printk("%s enter\n", __func__);
free = MAX_DEVICE_BUFFER_SIZE - *ppos;
need_write = free > count ? count : free;
if (need_write == 0)
dev_warn(mydemodrv_device, "no space for write");
ret = copy_from_user(device_buffer + *ppos, buf, need_write);
if (ret == need_write)
return -EFAULT;
actual_write = need_write - ret;
*ppos += actual_write;
printk("%s: actual_write =%d, ppos=%d\n", __func__, actual_write, *ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
int ret;
device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
if (!device_buffer)
return -ENOMEM;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register misc device\n");
kfree(device_buffer);
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", DEMO_NAME);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
kfree(device_buffer);
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_AUTHOR("Henry_Fordham");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("character device read & write");
2.2 编译字节驱动模块
这里要注意,我们需要在QEMU+ARM的环境下插入内核模块,那么就要在QEMU+ARM的环境下编译模块,为了节省时间,可以先下载一个基础环境,如下:
https://github.com/figozhang/runninglinuxkernel_4.0
直接git clone这个环境下来,下载按照README安装,安装完后,编写刚才内核模块的Makefile,注意第一行BASEINCLUDE刚才下载下来的runninglinuxkernel_4.0,Makefile如下。
BASEINCLUDE ?= ~/tools/runninglinuxkernel_4.0
mytest-objs :=my_demodev.o
obj-m :=mydemo.o
all :
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
rm -f *.ko;
写完之后,make,可以看到my_demo.ko
2.3 插入到QEMU上的Linux内核中
首先,还是挂载一块ext4磁盘到Linux内核中,通过QEMU启动:
qemu-system-arm -M vexpress-a9 -smp 4 \
-m 1024M \
-kernel arch/arm/boot/zImage \
-append "rdinit=/linuxrc console=ttyAMA0 loglevel=8" \
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-sd ext4.img
如果不知道怎么创建一个ext4的磁盘,还是参考第一篇文章。
挂载我们的ext4磁盘到QEMU的mnt:
mount -t ext4 /dev/mmcblk0 /mnt/
同时在主机上将ext4挂载到一个文件目录,使之同步。具体参考第一篇文章。
QEMU中mnt下将mydemo.ko插入进去:
/mnt # ls
lost+found mydemo.ko
/mnt # insmod mydemo.ko
successed register char device : my_demo_dev
Major numbner =252,minor number =0
2.4 写用户层测试代码测试插入的内核模块
如下是用户级代码: test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define DEMO_DEV_NAME "/dev/my_demo_dev"
int main(){
char buffer[64];
int fd;
int ret;
size_t len;
char message[] = "Testing the virtual FIFO device";
char *read_buffer;
len = sizeof(message);
fd = open(DEMO_DEV_NAME, O_RDWR);
if (fd < 0){
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
ret = write(fd, message, len);
if (ret != len){
printf("cannot write on device %d, ret=%d", fd, ret);
return -1;
}
read_buffer = malloc(2*len);
memset(read_buffer, 0, 2*len);
close(fd);
fd = open(DEMO_DEV_NAME, O_RDWR);
if (fd < 0){
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
ret = read(fd, read_buffer, 2*len);
printf("read %d bytes\n", ret);
printf("read buffer=%s\n",read_buffer);
close(fd);
return 0;
}
编译:
arm-linux-gnueabi-gcc test.c -o test --static
得到一个可执行文件test,将这个文件还是同步到qemu的内核文件系统ext4.img中,然后执行:
Please press Enter to activate this console.
/ # ls
bin etc mnt sbin tmp
dev linuxrc proc sys usr
/ # mount -t ext4 /dev/mmcblk0 /mnt/
EXT4-fs (mmcblk0): recovery complete
EXT4-fs (mmcblk0): mounted filesystem with ordered data mode. Opts: (null)
/ # cd mnt/
/mnt # ls
lost+found mydemo.ko test_virtual
/mnt # insmod mydemo.ko
succeeded register char device: my_demo_dev
/mnt # mknod /dev/demo_drv c 252 0
/mnt # ./test_virtual
random: nonblocking pool is initialized
demodrv_open: major=10, minor=58
demodrv_write enter
demodrv_write: actual_write =32, ppos=0
demodrv_open: major=10, minor=58
demodrv_read enter
demodrv_read, actual_readed=64, pos=0
read 64 bytes
read buffer=Testing the virtual FIFO device
可以看到,从用户层代码读取到了内核模块的信息。
3 KFIFO改进设备驱动
3.1 改进原因
可以从刚才的驱动程序代码中观察到,在读之前调用了一次open函数,在写之前也调用了一次open函数,这正是因为没有考虑到读和写的并行管理问题,所以在对应的测试程序中,需要重启设备将数据读出来。
所以如果要改进的地点就是能够并行的管理读和写进程。Linux内核实现了一个称为KFIFO的环形缓冲机制,它可以在一个读者线程和一个写者进程并发执行的场景下,无需使用额外的加锁来保证环形缓冲区的数据安全。KFIFO提供的接口函数定义在 include/linux/kfifo.h 文件中。
#define kfifo_from_user(fifo, from, len, copied)
#define kfifo_to_user(fifo, to, len, copied)
fifo表示使用的哪个环形缓存区。
from表示用户空间缓冲区的起始地址,len表示要复制多少个元素,copied保存了成功复制元素的数量。
to表示从fifo缓存空间读出到用户空间缓冲区的起始地址,其他不变。
所以,要改动的地方只有字符模块的 demodrv_read 和 demodrv_write 模块,将主要输入输出函数改为kfifo_from_user和kfifo_to_user。
3.2 带有KFIFO环形缓冲的虚拟驱动模块
带有KFIFO环形缓冲的虚拟驱动模块代码如下:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
DEFINE_KFIFO(mydemo_fifo, char, 64);
#define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
/*virtual FIFO device's buffer*/
static char *device_buffer;
#define MAX_DEVICE_BUFFER_SIZE 64
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int actual_readed;
int ret;
ret = kfifo_to_user(&mydemo_fifo, buf, count, &actual_readed);
if (ret)
return -EIO;
printk("%s, actual_readed=%d, pos=%lld\n",__func__, actual_readed, *ppos);
return actual_readed;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned int actual_write;
int ret;
ret = kfifo_from_user(&mydemo_fifo, buf, count, &actual_write);
if (ret)
return -EIO;
printk("%s: actual_write =%d, ppos=%d\n", __func__, actual_write, *ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
int ret;
device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
if (!device_buffer)
return -ENOMEM;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register misc device\n");
kfree(device_buffer);
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", DEMO_NAME);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
kfree(device_buffer);
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_AUTHOR("Henry_Fordham");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("character device read & write");
之后重新编译,make
3.3 写用户层测试代码测试插入的内核模块
如下是用户级代码: test_virtual.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define DEMO_DEV_NAME "/dev/my_demo_dev"
int main(){
char buffer[64];
int fd;
int ret;
size_t len;
char message[] = "Testing the virtual FIFO device";
char *read_buffer;
len = sizeof(message);
fd = open(DEMO_DEV_NAME, O_RDWR);
if (fd < 0){
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
ret = write(fd, message, len);
if (ret != len){
printf("cannot write on device %d, ret=%d", fd, ret);
return -1;
}
read_buffer = malloc(2*len);
memset(read_buffer, 0, 2*len);
close(fd);
fd = open(DEMO_DEV_NAME, O_RDWR);
if (fd < 0){
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
ret = read(fd, read_buffer, 2*len);
printf("read %d bytes\n", ret);
printf("read buffer=%s\n",read_buffer);
close(fd);
return 0;
}
编译:
arm-linux-gnueabi-gcc test_virtual.c -o test --static
得到一个可执行文件test,将这个文件还是同步到qemu的内核文件系统ext4.img中,然后执行:
Please press Enter to activate this console.
/ # ls
bin etc mnt sbin tmp
dev linuxrc proc sys usr
/ # mount -t ext4 /dev/mmcblk0 /mnt/
EXT4-fs (mmcblk0): recovery complete
EXT4-fs (mmcblk0): mounted filesystem with ordered data mode. Opts: (null)
/ # cd mnt/
/mnt # ls
lost+found mydemo.ko test_virtual
/mnt # insmod mydemo.ko
succeeded register char device: my_demo_dev
/mnt # ./test_virtual
random: nonblocking pool is initialized
demodrv_open: major=10, minor=58
demodrv_write: actual_write =32, ppos=0
demodrv_open: major=10, minor=58
demodrv_read, actual_readed=32, pos=0
read 32 bytes
read buffer=Testing the virtual FIFO device
4 阻塞I/O与非阻塞I/O
4.1 非阻塞I/O和阻塞I/O的概念
非阻塞:进程发起I/O系统调用后,如果设备驱动的缓冲区没有数据,那么进程会返回一个错误而不会被阻塞。如果驱动缓冲区有数据,那么设备驱动把数据直接返回给用户进程。
阻塞:进程发起I/O系统调用后,如果设备的缓冲区没有数据,那么需要到硬件I/O中重新获取数据,进程会被阻塞,也就是休眠等待。直到数据准备好,进程才会被唤醒,并重新把数据返回给用户空间。
4.2 把虚拟设备驱动改成非阻塞模式
4.2.1 非阻塞模式下的虚拟设备驱动
驱动代码如下:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
DEFINE_KFIFO(mydemo_fifo, char, 64);
#define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
/*virtual FIFO device's buffer*/
static char *device_buffer;
#define MAX_DEVICE_BUFFER_SIZE 64
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int actual_readed;
int ret;
if (kfifo_is_empty(&mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
}
ret = kfifo_to_user(&mydemo_fifo, buf, count, &actual_readed);
if (ret)
return -EIO;
printk("%s, actual_readed=%d, pos=%lld\n",__func__, actual_readed, *ppos);
return actual_readed;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned int actual_write;
int ret;
if (kfifo_is_full(&mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
}
ret = kfifo_from_user(&mydemo_fifo, buf, count, &actual_write);
if (ret)
return -EIO;
printk("%s: actual_write =%d, ppos=%d\n", __func__, actual_write, *ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
int ret;
device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
if (!device_buffer)
return -ENOMEM;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register misc device\n");
kfree(device_buffer);
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", DEMO_NAME);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
kfree(device_buffer);
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_AUTHOR("Henry_Fordham");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("character device read & write");
4.2.2 编译非阻塞模式下的虚拟设备驱动
这里要注意,我们需要在QEMU+ARM的环境下插入内核模块,那么就要在QEMU+ARM的环境下编译模块,为了节省时间,可以先下载一个基础环境,如下:
https://github.com/figozhang/runninglinuxkernel_4.0
直接git clone这个环境下来,下载按照README安装,安装完后,编写刚才内核模块的Makefile,注意第一行BASEINCLUDE刚才下载下来的runninglinuxkernel_4.0,Makefile如下。
BASEINCLUDE ?= ~/tools/runninglinuxkernel_4.0
mytest-objs :=my_demodev.o
obj-m :=mydemo.o
all :
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
rm -f *.ko;
写完之后,make,可以看到my_demo.ko
4.2.3 写用户层测试代码测试插入的内核模块
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define DEMO_DEV_NAME "/dev/my_demo_dev"
int main(){
int fd;
int ret;
size_t len;
char message[] = "Testing the virtual FIFO device";
char *read_buffer;
len = sizeof(message);
read_buffer = malloc(2*len);
memset(read_buffer, 0, 2*len);
fd = open(DEMO_DEV_NAME, O_RDWR | O_NONBLOCK);
if (fd < 0){
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
//First read the data
ret = read(fd, read_buffer, 2*len);
printf("read %d bytes\n", ret);
printf("read buffer=%s\n", read_buffer);
//Second, write to the device
ret = write(fd, message, len);
if (ret != len)
printf("have write %d bytes\n", ret);
//Third, write to the device
ret = write(fd, message, len);
if (ret != len)
printf("have write %d bytes\n", ret);
//First read the data
ret = read(fd, read_buffer, 2*len);
printf("read %d bytes\n", ret);
printf("read buffer=%s\n", read_buffer);
close(fd);
return 0;
}
编译:
arm-linux-gnueabi-gcc test_virtual.c -o test --static
得到一个可执行文件test,将这个文件还是同步到qemu的内核文件系统ext4.img中,然后执行:
Please press Enter to activate this console.
/ # mount -t ext4 /dev/mmcblk0 /mnt/
EXT4-fs (mmcblk0): recovery complete
EXT4-fs (mmcblk0): mounted filesystem with ordered data mode. Opts: (null)
/ # cd mnt/
/mnt # ls
lost+found mydemo.ko test_virtual
/mnt # insmod mydemo.ko
succeeded register char device: my_demo_dev
/mnt # ./test_virtual
random: nonblocking pool is initialized
demodrv_open: major=10, minor=58
read -1 bytes
read buffer=
demodrv_write: actual_write =32, ppos=0
demodrv_write: actual_write =32, ppos=0
demodrv_read, actual_readed=64, pos=0
read 64 bytes
read buffer=Testing the virtual FIFO device
// mydemo.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
DEFINE_KFIFO(mydemo_fifo, char, 64);
#define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
/*virtual FIFO device's buffer*/
static char *device_buffer;
#define MAX_DEVICE_BUFFER_SIZE 64
struct mydemo_device {
const char *name;
struct device *dev;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
};
struct mydemo_private_data {
struct mydemo_device *device;
char name[64];
};
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
int actual_readed;
int ret;
if (kfifo_is_empty(&mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("%s pid=%d, going to sleep\n",__func__, current->pid);
ret = wait_event_interruptible(device->read_queue,
!kfifo_is_empty(&mydemo_fifo));
if (ret)
return ret;
}
ret = kfifo_to_user(&mydemo_fifo, buf, count, &actual_readed);
if (ret)
return -EIO;
if (!kfifo_is_full(&mydemo_fifo)){
wake_up_interruptible(&device->write_queue);
}
printk("%s, actual_readed=%d, pos=%lld\n",__func__, actual_readed, *ppos);
return actual_readed;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct mydemo_private_data *data = file->private_data;
struct mydemo_device *device = data->device;
unsigned int actual_write;
int ret;
if (kfifo_is_full(&mydemo_fifo)){
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("%s pid=%d, going to sleep\n",__func__, current->pid);
ret = wait_event_interruptible(device->write_queue,
!kfifo_is_full(&mydemo_fifo));
if (ret)
return ret;
}
ret = kfifo_from_user(&mydemo_fifo, buf, count, &actual_write);
if (ret)
return -EIO;
if (!kfifo_is_full(&mydemo_fifo)){
wake_up_interruptible(&device->read_queue);
}
printk("%s: actual_write =%d, ppos=%lld\n", __func__, actual_write, *ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
struct mydemo_device *device;
int ret;
device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
if (!device_buffer)
return -ENOMEM;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register misc device\n");
kfree(device_buffer);
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", DEMO_NAME);
init_waitqueue_head(&device->read_queue);
init_waitqueue_head(&device->write_queue);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
kfree(device_buffer);
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_AUTHOR("Henry_Fordham");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("character device read & write");