字符设备驱动--包含字符设备、信号量、等待队列、定时器


本程序转自网路,有些地方有确实,但是内容不错,值得学习,后来补充发在博客作为帮助!

最近又重读了 LDD3(Linux Device Driver, 3rd Edition) , 对LDD的前面一部分, 即如何写一个字符设备进行了一个小 结. 希望能够对初学者有用. 

对于下面这一段代码, 演示了如何创建一个字符设备, 涉及到如下知识点: 
1. 如何创建字 符设备, 2. 使用信号量进行互斥, 3. 创建等待队列, 4. 设计一个定时器 
分别包括了LDD3中第二, 三, 五, 六, 七章中的 小部分内容. 

[CODE] 

#include<linux/module.h>

#include<linux/ioctl.h>

#include<linux/mm.h>

#include<linux/kernel.h>

#include<linux/init.h>

#include<linux/cdev.h>

#include<linux/types.h>

#include<linux/device.h>

#include<linux/fs.h>

#include<linux/sched.h>

#include<linux/delay.h>

#include<linux/jiffies.h>

#include<linux/timer.h>

#include<asm/uaccess.h>

 

 

MODULE_AUTHOR("csdn");

MODULE_LICENSE("GPL");

 

static struct semaphore my_lock;

static wait_queue_head_t my_wait;

static int flag = 0;

int major = 0;

int minor = 0;

dev_t devid;

struct timer_list my_timer;

struct class *myclass;

static struct device *dev;

 



/* 
 * my_handler 是定时器处理函数, 其将flag设定为1, 并唤醒等待队列上的进程. 
 */  
void my_handler(unsigned long data) 

        printk("in handler function/n"); 
        flag = 1; 
        wake_up_interruptible(&my_wait); 
        printk("wake it up/n"); 

/* 
 * my_read 由char_read调用, 当flag为0时就将自己放置到等待队列中去. 并阻塞当前进 程 
 */  
int my_read(int i) 

        printk("waiting.../n"); 
        wait_event_interruptible(my_wait, flag != 0); 
        printk("I am waked/n"); 
        flag = 0; 
        return (i + 10); 

/* 
 * char_open 是 打开设备时调用的函数, try_module_get()表示当前模块正在被使用中 
 */  
static int char_open(struct inode *i, struct file* filp) 

        printk("char_open is called/n"); 
        try_module_get(THIS_MODULE); 
        return 0; 


static int char_release(struct inode *i, struct file* filp) 

        module_put(THIS_MODULE); 
        return 0; 

/* 
 * char_read 是读设备时调用的函数, 首先使用信号量完成互斥操作, 然后进行定时器的初始化, 5*HZ表示等待5秒钟.  
 * 最 后调用copy_to_user将数据拷贝给用户态的程序. 
 */  
static ssize_t char_read(struct file* filp, char *buffer, size_t count, loff_t *ppos) 

        int ret = 0; 
        if (down_interruptible(&my_lock)) 
                return -ERESTARTSYS; 
        printk("char_read is called/n"); 
        init_timer(&my_timer); 
        my_timer.expires = jiffies + 5 * HZ; 
        my_timer.data = 0; 
        my_timer.function = my_handler; 
        add_timer(&my_timer); 

        ret = my_read(0); 
        printk("return value is %d/n", ret); 
        del_timer(&my_timer); 
        copy_to_user(buffer, (char*)&ret, sizeof(int)); 
        up(&my_lock); 
        return (sizeof(int)); 


static struct file_operations char_fops = 

        .open = char_open, 
        .read = char_read, 
        .release = char_release 
}; 

static struct cdev *my_cdev; 
/* 
 * char_init 模块 的注册函数. 
 */  

static int __init char_init(void)

{

    int err;

    if(major)

    {

        devid = MKDEV(major, minor);

 

 

        err = register_chrdev_region(devid, 1, "chardev");

 

 

    }

 

 

    else

 

    {

 

        err = alloc_chrdev_region(&devid, 0, 1, "chardev");

 

 

        major = MAJOR(devid);

 

 

        printk("The major number is %d/n", major);

 

 

    }

 

 

    my_cdev = cdev_alloc();

 

    my_cdev->ops = &char_fops;

 

    cdev_init(my_cdev, &char_fops);

 

    err = cdev_add(my_cdev, devid, 1);

 

    if(err)

 

    {

 

        printk("error/n");

 

 

        return -1;

 

 

    }

 

 

    myclass = class_create(THIS_MODULE, "myclass");

 

    if(IS_ERR(myclass))

 

    {

 

        printk("Err:fail in creating class");

 

 

        return -1;

 

 

    }

 

    dev = device_create(myclass, NULL, devid, NULL, "chardev");

 

    printk("creat the device seccuc/n");

 

    init_MUTEX(&my_lock);

 

    init_waitqueue_head(&my_wait);

 

 

    return 0;

}

 

static void __exit char_exit(void)

{

 

    device_destroy(myclass, devid);

 

    class_destroy(myclass);

 

 

    cdev_del(my_cdev);

 

    printk("rmmod is called/n");

 

    unregister_chrdev_region(devid, 1);

}


module_init(char_init); 
module_exit(char_exit); 
[/CODE] 

下面对代码进行一些简要的说明. 
1. 首 先是char_init函数, 该函数完成了对字符设备的注册功能. 如果注册成功, 则输出字符设备的主设备号. 另一方面, 还完成 了对信号量的初始化, 以及对等待队列的初始化. 因此, 当insmod结束之后, 正常情况下就完成了对字符设备的注册等一系列的初始化动作了.  
2. 然 后再看打开设备的操作char_open. 当你在程序中调用open时, 就会去调用这个函数. 这个函数中有一个 try_module_get(THIS_MODULE);它表示当前这个模块处于使用之中. 此时通过lsmod可以看到used一栏中不为0. 如果 此时调用rmmod去卸载这个模块, 则会出错. 提示当前模块正在使用中, 不能被卸载.  
3. 打开之后就是去读这个字符设备. 此时在 程序中调用read时, 就会去调用模块中的char_read这个函数. 这个函数首先调用 down_interruptible函数, 进行锁定. 这样, 只有一个进程能够运行char_read这个函数. 然后再是初始化定时器. 时间为 5秒钟. 之后再去调用my_read这个函数. 在这个函数中检查flag是否为0, 如果为0了, 则将当前进程放到等待队列中去, 等待其它的进程 将自己唤醒. 当过了五秒钟之后, 由于定时器的作用, 调用了my_handler这个函数, 它一方面将flag的值设为1, 另一方面调用 wake_up_interruptible将该等待队列上的进程唤醒. 因此, 此时my_read函数又开始继续往下面执行了. 剩下的工作就是删除 定时器, 并将值传递给用户态的程序.  
4. 最后用户态的程序调用close()时则关于了这个设备, 此时调用了 module_put(THIS_MODULE); 与之前的try_module_get相对应, 即表示当前模块没有被占用, 此时可以调用 rmmod将模块卸载掉. 

下面就编写一个用户态的程序来试验这个字符设备 
test.c 
[CODE] 
#include<stdio.h> 
#include<stdlib.h> 
#include<unistd.h> 
#include<fcntl.h> 
#include<sys/types.h> 

int main() 

        int fd; 
        int i; 
        fd = open("/dev/chardev", O_RDONLY); 
        if (fd < 0) 
        { 
                printf("error/n"); 
                return -1; 
        } 

        if (read(fd, &i, sizeof(int)) < 0) 
        { 
                printf("read error/n"); 
                return -1; 
        } 
        printf("i = %d/n", i); 
        return 0; 

[/CODE] 

最后是实验步骤. 
1. 首先编写Makefile.  
[CODE] 

ifeq ($(KERNELRELEASE), )

KERNELDIR ?= /usr/src/linux-headers-2.6.35-28-generic

PWD := $(shell pwd)

modules:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:

rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_version

.PHONY:

modules modules_install clean

else

obj-m := char.o

endif

 

[/CODE] 

2. 在当前目录下使用make进行编译, 然后再使用sudo install ./char.ko将模块加载进内核. 此时使用dmesg可以查看系统给该字符设备分配的设备号是多少. 假如为249. 则该字符设备文件已经自动创建,在/dev/下生成一个chardev文件。

3. 编译 test.c, 然后运行, 在运行时, 其等待了5秒钟, 即为等待定时器的那个过程. 最后得到输出的结果. 此时再通过dmesg命令可以查看模块 中函数的调用过程.  
4. 实验完之后调用sudo rmmod ./char.ko卸载该模块, 并调用make clean清除一些临时文件.  

下面的结果为我在自己的机器上运行后通过dmesg打印出来的结果. 
[QUOTE] 
The major number is 253 
char_open is called 
char_read is called 
waiting... 
in handler function 
wake it up 
I am waked 
return value is 10 
[/QUOTE] 

    另 外, 如果test程序在等待时执行rmmod chardev时会提示设备正在忙. 就是前面的try_module_get()所 引起的. 同时如果在两个终端运行test这个程序, 则第二个test程序会阻塞, 并在第一个test程序结束之后才得以执行. 防止了并发.  
    上 面对字符设备的注册, 信号量, 等待队列以及定时器的使用作了一个简单的介绍, 当然, LDD3前七章的内容远不止这些. 还有很多需要去参透的地 方.   
    我以前第一次看LDD的时候, 对里面的很多东西也不理解, 第一次看完之后完全不知道自己看了什么. 所以写这篇文章希望能 对刚开始看LDD的朋友有一点用处.

你可能感兴趣的:(timer,struct,Module,Semaphore,Class,buffer)