欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux驱动poll机制 mmap的实现 中断下半部的实现

程序员文章站 2022-07-14 11:34:26
...

内容:

1,多路复用——poll机制的实现
2,mmap的实现

3,中断的下半部

一,多路复用——poll机制的实现

应用空间:
    #include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
            struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
    例如: 使用poll同时监控键盘输入和按键
struct pollfd pfds[2];

        pfds[0].fd = STDIN_FILENO;          //监控标准输入
        pfds[0].events = POLLIN;            //是否可读

        pfds[1].fd = fd;            //按键
        pfds[1].events = POLLIN;        //是否可读

        while(1){
            ret = poll(pfds,2,-1);
            if(ret < 0){
                perror("poll");
                exit(1);
            }
            if(ret > 0){
                //判断是哪个设备有数据可读
            }
        }
----------------------------------------------------------------
内核驱动中在unsigned int button_drv_poll(struct file * filp, struct poll_table_struct * pts)函数中:

    1> 将当前的等待队列头注册到系统中  ------ VFS
        static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
        //功能:将当前的等待队列头注册到系统中  ------ VFS
        //参数1: struct file结构体的地址
        //参数2: 等待队列头
        //参数3: 与等待队列头相关的表

    2> 如果产生了按键中断-有数据可读,返回:POLLIN,没有数据返回:0
        unsigned int mask = 0;
        if(button_dev->have_data)
            mask |= POLLIN;
        return mask;

linux驱动poll机制 mmap的实现 中断下半部的实现
button_drv.c文件代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/ioctl.h>

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};
struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0
};
struct s5pv210_button *button_dev;

irqreturn_t button_irq_handler(int irqno, void * arg)
{
    int value;
    printk("---------%s------------\n",__FUNCTION__);
    //区分按键 ----按下 还是 松开

    value = gpio_get_value(S5PV210_GPH0(1));
    if(value){
        //松开
        printk("kernel: keydown up!\n");
        button_dev->event.code = KEY_DOWN;
        button_dev->event.value = 0;
    }else{
        //按下
        printk("kernel: keydown pressed!\n");
        button_dev->event.code = KEY_DOWN;
        button_dev->event.value = 1;
    }

    //此时有数据 
    button_dev->have_data = 1;

    //唤醒阻塞的进程
    wake_up_interruptible(&button_dev->wq_head);

    return IRQ_HANDLED;
}

int button_drv_open(struct inode *inode , struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}

ssize_t button_drv_read(struct file *filp, char __user * buf, size_t size , loff_t * flags)
{
    int ret;
    printk("---------%s------------\n",__FUNCTION__);
#if 1
    // 如果没有数据,则结束 ,如果有数据,则copy_to_user返回数据给应用空间
    if((filp->f_flags & O_NONBLOCK) && !button_dev->have_data)
        return -EAGAIN;
#endif
    //判断是否有资源可读
    wait_event_interruptible(button_dev->wq_head, button_dev->have_data);

    ret = copy_to_user(buf, &button_dev->event, size);
    if(ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }

    //清空event,同时将have_data赋0
    memset(&button_dev->event, 0,sizeof(button_dev->event));
    button_dev->have_data = 0;
    return size;
}

ssize_t button_drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *flag)
{

    printk("---------%s------------\n",__FUNCTION__);
    return size;
}

long button_drv_ioctl(struct file *filp , unsigned int cmd , unsigned long args)
{

    printk("---------%s------------\n",__FUNCTION__);

    return 0;
}
unsigned int button_drv_poll(struct file * filp, struct poll_table_struct * pts)
{
    int mask = 0; 
    printk("---------%s------------\n",__FUNCTION__);

    // 1,将等待队列头注册到系统中----VFS中
    poll_wait(filp,&button_dev->wq_head, pts);

    // 2,如果产生了按键中断-有数据可读,返回:POLLIN,没有数据返回:0
    if(button_dev->have_data)
        mask |= POLLIN;

        return mask;
}
int button_drv_close(struct inode *inode, struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}
struct file_operations fops = {
    .open = button_drv_open,
    .read = button_drv_read,
    .write = button_drv_write,
    .unlocked_ioctl = button_drv_ioctl,
    .poll = button_drv_poll,
    .release = button_drv_close,
};
static int __init button_init(void)
{
    int ret;
    printk("---------%s------------\n",__FUNCTION__);
    // 0, 初始化对象
    button_dev = kzalloc(sizeof(struct s5pv210_button), GFP_KERNEL);
    if(IS_ERR(button_dev)){
        printk("kzalloc error\n");
        return  -ENOMEM;
    }
    // 1,申请设备号 
    #if 0
    // 静态申请设备号
    button_dev->devno = MKDEV(256, 0);
    ret = register_chrdev_region(button_dev->devno, 1 , "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #else
    // 动态申请设备号
    ret = alloc_chrdev_region(&button_dev->devno, 10, 1, "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #endif
    //创建cdev
        // 1> 申请cdev的空间-----实例化cdev
            button_dev->cdev = cdev_alloc();
            if(IS_ERR(button_dev->cdev)){
                printk("cdev_alloc error\n");
                ret = PTR_ERR(button_dev->cdev);
                goto err_unregister;
            }
        // 2> 初始化cdev
        cdev_init(button_dev->cdev, &fops);

        // 3> 将cdev注册到内核中
        ret = cdev_add(button_dev->cdev, button_dev->devno, 1);

    // 2,创建设备文件

    button_dev->cls= class_create(THIS_MODULE,"button_class");
    if(IS_ERR(button_dev->cls)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->cls);
        goto err_unregister;
    }

    button_dev->dev= device_create(button_dev->cls, NULL, button_dev->devno, NULL, "button%d",5);
    if(IS_ERR(button_dev->dev)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->dev);
        goto err_class;
    }

    // 3,硬件初始化----中断申请
    button_dev->irqno = IRQ_EINT(1);
    ret = request_irq(button_dev->irqno, button_irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "eint1-keydown", NULL);
    if(ret < 0){
        printk("request_irq error\n");
        goto err_device;
    }

    //初始化等待队列头
    init_waitqueue_head(&button_dev->wq_head);

    return 0;
err_device:
    device_destroy(button_dev->cls, button_dev->devno);
err_class:
    class_destroy(button_dev->cls);
err_unregister:
    unregister_chrdev_region(button_dev->devno, 1);
err_free:
    kfree(button_dev);
    return ret;
}

static void __exit button_exit(void)
{
    printk("---------%s------------\n",__FUNCTION__);
    free_irq(button_dev->irqno, NULL);
    device_destroy(button_dev->cls, button_dev->devno);
    class_destroy(button_dev->cls);
    unregister_chrdev_region(button_dev->devno, 1);
    kfree(button_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");   //认证

Makefile文件代码

KERNEL_DIR = /home/lpf/1803/s5pv210/kernel/linux-3.0.8    #指定内核源码路径
CUR_DIR = $(shell pwd)     
SRC = test.c
MYAPP = test

all:
    #使make进入内核源码目录,并将当前目录下的源码作为内核的模块一起编译
    make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
    arm-none-linux-gnueabi-gcc -o $(MYAPP) $(SRC)

clean:
    #删除上面生成的文件
    make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
    rm $(MYAPP) .*.sw?
install:
    cp *.ko $(MYAPP) /opt/rootfs/drv_module

obj-m = button_drv.o

test.c应用层文件代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <poll.h>


//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};

int main(void)
{
    int fd;
    int ret;
    struct button_event event;
    char buf[100];

    fd = open("/dev/button5",O_RDWR);
    if(fd < 0){
    printf("open");
    exit(1);
    }


    //用poll实现同时监控标准输入和按键
    struct pollfd pfds[2];

    pfds[0].fd = STDIN_FILENO;      //监控标准输入
    pfds[0].events = POLLIN;        //是否可读

    pfds[1].fd = fd;        //按键
    pfds[1].events = POLLIN;        //是否可读

    while(1){
    ret = poll(pfds,2,-1);
    if(ret < 0){
        perror("poll");
        exit(1);
    }
    if(ret > 0){
        //判断哪个文件描述符上有数据可读
        if(pfds[0].revents && POLLIN){   //标准输入可读
        fgets(buf,100,stdin);
        printf("%s",buf);
        }
        if(pfds[1].revents && POLLIN){  //按键触发了中断
        memset(&event,0,sizeof(event));
        ret = read(fd,&event,sizeof(event));
        if(ret  < 0){
            perror("read");
            exit(1);
        }
        if(event.code == KEY_DOWN){  //判断是哪个按键
            if(event.value){  //按下
            printf("下键----按下!\n");
            }else{  //松开
            printf("下键----松开!\n");
            }
        }
        }
        sleep(1);
    }
    close(fd);
    return 0;
}

二,mmap的实现(LCD屏驱动中使用到该函数)

当需要在应用空间中使用使用物理内存,可以通过mmap映射

1 优点:
        1> 是文件IO的一种接口,可以在驱动中实现
        2> 在应用空间和内核空间之间传递数据时,效率比较高
        3> 使用方便,可以在应用空间直接调用mmap映射物理内存

2,实现:

    应用空间:

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
//参数1: 指定物理内存映射到虚拟空间的起始地址,一般为:NULL—系统自动分配
//参数2: 申请的空间长度
//参数3: 对内存的操作权限prot: PROT_EXEC PROT_READ PROT_WRITE PROT_NONE
//参数4: 是否允许其他进程映射该内存: MAP_SHARED MAP_PRIVATE
//参数5: 打开的文件
//参数6: 从物理内存偏移多少字节的位置开始映射

        //返回值: 成功--映射的虚拟空间的地址,失败---NULL
        int munmap(void *addr, size_t length);
        //参数1: mmap返回的地址
        //参数2: 映射的长度
        //返回值:成功--0, 失败--- -1
    -------------------------------------------------------------------------------
    驱动:

        // 1, 通过申请的虚拟空间找到对应的物理空间
        addr = virt_to_phys(button_dev->virt_mem);
        memset(button_dev->virt_mem,0,sizeof(button_dev->virt_mem));
        // 2,实现映射
        vma->vm_flags |= VM_IO;
        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

        if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
                        PAGE_SIZE, vma->vm_page_prot)) {
            printk(KERN_ERR "%s: io_remap_pfn_range failed\n",
                __func__);
            return -EAGAIN;
        }

linux驱动poll机制 mmap的实现 中断下半部的实现
linux驱动poll机制 mmap的实现 中断下半部的实现
阻塞非阻塞io实现
linux驱动poll机制 mmap的实现 中断下半部的实现
一,在module_init函数中

    struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
};
struct s5pv210_button *button_dev;
    //申请一块虚拟空间
    button_dev->virt_mem = kzalloc(PAGE_SIZE, GFP_KERNEL);
    if(IS_ERR(button_dev->virt_mem)){
        printk("kzalloc error\n");
        ret = PTR_ERR(button_dev->virt_mem);
        goto err_class;
    }

在int button_drv_mmap(struct file * filp, struct vm_area_struct * vma)中

struct mem_data{
    char buf[128];
};

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};
struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
};
struct s5pv210_button *button_dev;

//定义个描述按键信息的结构体
struct buttons{
    char *name;         //名称
    int code;       //键值
    unsigned int irqno;  //中断号
    int gpio;       //对应的gpio口的号码
    unsigned long flags;   //触发方式
};

int button_drv_mmap(struct file * filp, struct vm_area_struct * vma)
{
    unsigned int addr;
    printk("---------%s------------\n",__FUNCTION__);

    // 1, 通过申请的虚拟空间找到对应的物理空间
    addr = virt_to_phys(button_dev->virt_mem);
    memset(button_dev->virt_mem,0,sizeof(button_dev->virt_mem));
    // 2,实现映射
    vma->vm_flags |= VM_IO;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
                    PAGE_SIZE, vma->vm_page_prot)) {
        printk(KERN_ERR "%s: io_remap_pfn_range failed\n",
            __func__);
        return -EAGAIN;
    }

    return 0;
}

三,在long button_drv_ioctl(struct file *filp , unsigned int cmd , unsigned long args)中

#define BUTTON_IOC_GET_DATA  _IO('I', 0x1234)
struct mem_data{
    char buf[128];
};

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};
struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
};
struct s5pv210_button *button_dev;

//定义个描述按键信息的结构体
struct buttons{
    char *name;         //名称
    int code;       //键值
    unsigned int irqno;  //中断号
    int gpio;       //对应的gpio口的号码
    unsigned long flags;   //触发方式
};

long button_drv_ioctl(struct file *filp , unsigned int cmd , unsigned long args)
{
    int ret;
    void __user *argp;
    struct mem_data data;
    printk("---------%s------------\n",__FUNCTION__);

    argp = (void __user *)args;
    switch(cmd){
        case BUTTON_IOC_GET_DATA:
            memset(&data.buf,0,sizeof(data.buf));
            memcpy(data.buf, button_dev->virt_mem, sizeof(data.buf));
            ret = copy_to_user(argp, &data, sizeof(data));
            if(ret > 0){
                return -EFAULT;
            }
            break;
        default:
            printk("unkown cmd!\n");
    }

    return 0;
}

button_drv.c文件代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/ioctl.h>
#include <asm/string.h>
#include <asm/pgtable.h>

#define BUTTON_IOC_GET_DATA  _IO('I', 0x1234)
struct mem_data{
    char buf[128];
};

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};
struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
};
struct s5pv210_button *button_dev;

//定义个描述按键信息的结构体
struct buttons{
    char *name;         //名称
    int code;       //键值
    unsigned int irqno;  //中断号
    int gpio;       //对应的gpio口的号码
    unsigned long flags;   //触发方式
};

//定义一个存储多个按键信息的集合
struct buttons buttons_set[] = {
    [0] = {
        .name = "key1_up",
        .code = KEY_UP,
        .irqno = IRQ_EINT(0),
        .gpio = S5PV210_GPH0(0),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [1] = {
        .name = "key2_down",
        .code = KEY_DOWN,
        .irqno = IRQ_EINT(1),
        .gpio = S5PV210_GPH0(1),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [2] = {
        .name = "key3_left",
        .code = KEY_LEFT,
        .irqno = IRQ_EINT(2),
        .gpio = S5PV210_GPH0(2),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [3] = {
        .name = "key4_right",
        .code = KEY_RIGHT,
        .irqno = IRQ_EINT(3),
        .gpio = S5PV210_GPH0(3),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
};
irqreturn_t button_irq_handler(int irqno, void * arg)
{
    int value;
    struct buttons *p;
    printk("---------%s------------\n",__FUNCTION__);
    //区分按键 ----按下 还是 松开

    //获取当前触发中断的按键信息
    p = (struct buttons*)arg;

    value = gpio_get_value(p->gpio);
    if(value){
        //松开
        printk("kernel: %s up!\n",p->name);
        button_dev->event.code = p->code;
        button_dev->event.value = 0;
    }else{
        //按下
        printk("kernel: %s pressed!\n",p->name);
        button_dev->event.code = p->code;
        button_dev->event.value = 1;
    }

    //此时有数据 
    button_dev->have_data = 1;

    //唤醒阻塞的进程
    wake_up_interruptible(&button_dev->wq_head);

    return IRQ_HANDLED;
}

int button_drv_open(struct inode *inode , struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}

ssize_t button_drv_read(struct file *filp, char __user * buf, size_t size , loff_t * flags)
{
    int ret;
    printk("---------%s------------\n",__FUNCTION__);
#if 1
    // 如果没有数据,则结束 ,如果有数据,则copy_to_user返回数据给应用空间
    if((filp->f_flags & O_NONBLOCK) && !button_dev->have_data)
        return -EAGAIN;
#endif
    //判断是否有资源可读
    wait_event_interruptible(button_dev->wq_head, button_dev->have_data);

    ret = copy_to_user(buf, &button_dev->event, size);
    if(ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }

    //清空event,同时将have_data赋0
    memset(&button_dev->event, 0,sizeof(button_dev->event));
    button_dev->have_data = 0;
    return size;
}

ssize_t button_drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *flag)
{

    printk("---------%s------------\n",__FUNCTION__);
    return size;
}

long button_drv_ioctl(struct file *filp , unsigned int cmd , unsigned long args)
{
    int ret;
    void __user *argp;
    struct mem_data data;
    printk("---------%s------------\n",__FUNCTION__);

    argp = (void __user *)args;
    switch(cmd){
        case BUTTON_IOC_GET_DATA:
            memset(&data.buf,0,sizeof(data.buf));
            memcpy(data.buf, button_dev->virt_mem, sizeof(data.buf));
            ret = copy_to_user(argp, &data, sizeof(data));
            if(ret > 0){
                return -EFAULT;
            }
            break;
        default:
            printk("unkown cmd!\n");
    }

    return 0;
}

int button_drv_mmap(struct file * filp, struct vm_area_struct * vma)
{
    unsigned int addr;
    printk("---------%s------------\n",__FUNCTION__);

    // 1, 通过申请的虚拟空间找到对应的物理空间
    addr = virt_to_phys(button_dev->virt_mem);
    memset(button_dev->virt_mem,0,sizeof(button_dev->virt_mem));
    // 2,实现映射
    vma->vm_flags |= VM_IO;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
                    PAGE_SIZE, vma->vm_page_prot)) {
        printk(KERN_ERR "%s: io_remap_pfn_range failed\n",
            __func__);
        return -EAGAIN;
    }

    return 0;
}

unsigned int button_drv_poll(struct file * filp, struct poll_table_struct * pts)
{
    int mask = 0; 
    printk("---------%s------------\n",__FUNCTION__);

    // 1,将等待队列头注册到系统中----VFS中
    poll_wait(filp,&button_dev->wq_head, pts);

    // 2,如果产生了按键中断-有数据可读,返回:POLLIN,没有数据返回:0
    if(button_dev->have_data)
        mask |= POLLIN;

        return mask;
}


int button_drv_close(struct inode *inode, struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}
struct file_operations fops = {
    .open = button_drv_open,
    .read = button_drv_read,
    .write = button_drv_write,
    .unlocked_ioctl = button_drv_ioctl,
    .poll = button_drv_poll,
    .mmap = button_drv_mmap,
    .release = button_drv_close,
};
static int __init button_init(void)
{
    int ret,i;
    printk("---------%s------------\n",__FUNCTION__);
    // 0, 初始化对象
    button_dev = kzalloc(sizeof(struct s5pv210_button), GFP_KERNEL);
    if(IS_ERR(button_dev)){
        printk("kzalloc error\n");
        return  -ENOMEM;
    }
    // 1,申请设备号 
    #if 0
    // 静态申请设备号
    button_dev->devno = MKDEV(256, 0);
    ret = register_chrdev_region(button_dev->devno, 1 , "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #else
    // 动态申请设备号
    ret = alloc_chrdev_region(&button_dev->devno, 10, 1, "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #endif
    //创建cdev
        // 1> 申请cdev的空间-----实例化cdev
            button_dev->cdev = cdev_alloc();
            if(IS_ERR(button_dev->cdev)){
                printk("cdev_alloc error\n");
                ret = PTR_ERR(button_dev->cdev);
                goto err_unregister;
            }
        // 2> 初始化cdev
        cdev_init(button_dev->cdev, &fops);

        // 3> 将cdev注册到内核中
        ret = cdev_add(button_dev->cdev, button_dev->devno, 1);

    // 2,创建设备文件

    button_dev->cls= class_create(THIS_MODULE,"button_class");
    if(IS_ERR(button_dev->cls)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->cls);
        goto err_unregister;
    }

    button_dev->dev= device_create(button_dev->cls, NULL, button_dev->devno, NULL, "button%d",5);
    if(IS_ERR(button_dev->dev)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->dev);
        goto err_class;
    }

    // 3,硬件初始化----中断申请
    for( i = 0; i <ARRAY_SIZE(buttons_set); i++){
        ret = request_irq(buttons_set[i].irqno, button_irq_handler,buttons_set[i].flags,buttons_set[i].name, &buttons_set[i]);
        if(ret < 0){
            printk("request_irq error\n");
            goto err_device;
        }
    }
    //初始化等待队列头
    init_waitqueue_head(&button_dev->wq_head);

    //申请一块虚拟空间
    button_dev->virt_mem = kzalloc(PAGE_SIZE, GFP_KERNEL);
    if(IS_ERR(button_dev->virt_mem)){
        printk("kzalloc error\n");
        ret = PTR_ERR(button_dev->virt_mem);
        goto err_class;
    }


    return 0;
err_device:
    device_destroy(button_dev->cls, button_dev->devno);
err_class:
    class_destroy(button_dev->cls);
err_unregister:
    unregister_chrdev_region(button_dev->devno, 1);
err_free:
    kfree(button_dev);
    return ret;
}

static void __exit button_exit(void)
{
    int i;
    printk("---------%s------------\n",__FUNCTION__);
    for( i = 0; i <ARRAY_SIZE(buttons_set); i++)
        free_irq(buttons_set[i].irqno, &buttons_set[i]);
    device_destroy(button_dev->cls, button_dev->devno);
    class_destroy(button_dev->cls);
    unregister_chrdev_region(button_dev->devno, 1);
    kfree(button_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");   //认证









Makefile文件代码

KERNEL_DIR = /home/lpf/1803/s5pv210/kernel/linux-3.0.8    #指定内核源码路径
CUR_DIR = $(shell pwd)     
SRC = test.c
MYAPP = test

all:
    #使make进入内核源码目录,并将当前目录下的源码作为内核的模块一起编译
    make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
    arm-none-linux-gnueabi-gcc -o $(MYAPP) $(SRC)

clean:
    #删除上面生成的文件
    make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
    rm $(MYAPP) .*.sw?
install:
    cp *.ko $(MYAPP) /opt/rootfs/drv_module

obj-m = button_drv.o

test.c应用层文件代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <poll.h>
#include <sys/mman.h>

#define PAGE_SHIFT            12
#define PAGE_SIZE   (1UL << PAGE_SHIFT)

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};

#define BUTTON_IOC_GET_DATA  _IO('I', 0x1234)
struct mem_data{
    char buf[128];
};

int main(void)
{
    int fd;
    int ret,on;
    struct button_event event;
    char buf[100];

    fd = open("/dev/button5",O_RDWR);
    if(fd < 0){
    printf("open");
    exit(1);
    }

    //测试mmap的功能
    char str[128];
    struct mem_data data;
    char * addr = mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == NULL){
    perror("mmap");
    exit(1);
    }
    //向映射的物理空间中写入一个字符串测试
    printf("请输入一个字符串:");
    memset(str,0,128);
    fgets(str,128,stdin);
    str[strlen(str)-1] = '\0';
    memcpy(addr,str,strlen(str));
    sleep(1);

    //测试数据有没有真正的写入到物理空间
    memset(&data,0,sizeof(data));
    ret = ioctl(fd,BUTTON_IOC_GET_DATA,&data);
    if(ret < 0){
    perror("ioctl");
    exit(1);
    }
    printf("data.buf = %s\n",data.buf);
    sleep(1);

    //用poll实现同时监控标准输入和按键
    struct pollfd pfds[2];

    pfds[0].fd = STDIN_FILENO;      //监控标准输入
    pfds[0].events = POLLIN;        //是否可读

    pfds[1].fd = fd;        //按键
    pfds[1].events = POLLIN;        //是否可读

    while(1){
    ret = poll(pfds,2,-1);
    if(ret < 0){
        perror("poll");
        exit(1);
    }
    if(ret > 0){
        //判断哪个文件描述符上有数据可读
        if(pfds[0].revents && POLLIN){   //标准输入可读
        fgets(buf,100,stdin);
        printf("%s",buf);
        }
        if(pfds[1].revents && POLLIN){  //按键触发了中断
        memset(&event,0,sizeof(event));
        ret = read(fd,&event,sizeof(event));
        if(ret  < 0){
            perror("read");
            exit(1);
        }
        switch(event.code){
            case KEY_UP:
            if(event.value){  //按下
                printf("上键----按下!\n");
            }else{  //松开
                printf("上键----松开!\n");
            }
            break;
            case KEY_DOWN:
            if(event.value){  //按下
                printf("下键----按下!\n");
            }else{  //松开
                printf("下键----松开!\n");
            }
            break;
            case KEY_LEFT:
            if(event.value){  //按下
                printf("左键----按下!\n");
            }else{  //松开
                printf("左键----松开!\n");
            }
            break;
            case KEY_RIGHT:
            if(event.value){  //按下
                printf("右键----按下!\n");
            }else{  //松开
                printf("右键----松开!\n");
            }
            break;
        }
        }
        sleep(1);
    }
    }


    close(fd);
    return 0;
}

三,中断下半部的实现
1,概念:
将中断处理函数中不太重要的事务(不耗时间的事务)交给内核线程延迟处理,这样处理中断的方式,称为中断下半部.
2,初始化tasklet
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

    //参数1: struct tasklet_struct 结构体变量的地址
    //参数2: 中断下半部执行的函数
    //参数3: 传给中断下半部执行函数的参数

3,实现中断下半部函数
    //中断下半部执行的函数
    void xxx_tasklet(unsigned long data)
    {
        printk("---------%s------------\n",__FUNCTION__);

        //中断下半部执行的代码
            ........
    }
4,需要在中断处理函数中将tasklet对象加入到内核线程
    static inline void tasklet_schedule(struct tasklet_struct *t)

一,在module_init函数中加入的代码

struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
    struct tasklet_struct tasklet;
};
struct s5pv210_button *button_dev;
    //中断下半部的初始化 : tasklet的初始化
    tasklet_init(&button_dev->tasklet, button_irq_tasklet,12300);

二,在irqreturn_t button_irq_handler(int irqno, void * arg)中断处理函数中加入的代码

struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
    struct tasklet_struct tasklet;
};
struct s5pv210_button *button_dev;

    //在中断处理函数中 将tasklet对象加入到内核线程中
    tasklet_schedule(&button_dev->tasklet);

三,中断下半部处理函数实现的代码

struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
    struct tasklet_struct tasklet;
};
struct s5pv210_button *button_dev;

    printk("---------%s------------\n",__FUNCTION__);


    //此时有数据 
    button_dev->have_data = 1;

    //唤醒阻塞的进程
    wake_up_interruptible(&button_dev->wq_head);

总代码button_drv.c代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/ioctl.h>
#include <asm/string.h>
#include <asm/pgtable.h>

#define BUTTON_IOC_GET_DATA  _IO('I', 0x1234)
struct mem_data{
    char buf[128];
};

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};
struct s5pv210_button{
    //unsigned int major;
    dev_t devno;
    struct class *cls; 
    struct device * dev;
    struct cdev * cdev;
    unsigned int irqno;
    struct button_event event;
    wait_queue_head_t wq_head;

    int have_data;      // 如果触发了中断 ,有数据产出,则 为 1, 否则 为 0

    void * virt_mem;
    struct tasklet_struct tasklet;
};
struct s5pv210_button *button_dev;

//定义个描述按键信息的结构体
struct buttons{
    char *name;         //名称
    int code;       //键值
    unsigned int irqno;  //中断号
    int gpio;       //对应的gpio口的号码
    unsigned long flags;   //触发方式
};


//定义一个存储多个按键信息的集合
struct buttons buttons_set[] = {
    [0] = {
        .name = "key1_up",
        .code = KEY_UP,
        .irqno = IRQ_EINT(0),
        .gpio = S5PV210_GPH0(0),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [1] = {
        .name = "key2_down",
        .code = KEY_DOWN,
        .irqno = IRQ_EINT(1),
        .gpio = S5PV210_GPH0(1),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [2] = {
        .name = "key3_left",
        .code = KEY_LEFT,
        .irqno = IRQ_EINT(2),
        .gpio = S5PV210_GPH0(2),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
    [3] = {
        .name = "key4_right",
        .code = KEY_RIGHT,
        .irqno = IRQ_EINT(3),
        .gpio = S5PV210_GPH0(3),
        .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
    },
};

//中断下半部执行的函数
void button_irq_tasklet(unsigned long data)
{
    printk("---------%s------------\n",__FUNCTION__);


    //此时有数据 
    button_dev->have_data = 1;

    //唤醒阻塞的进程
    wake_up_interruptible(&button_dev->wq_head);
}

irqreturn_t button_irq_handler(int irqno, void * arg)
{
    int value;
    struct buttons *p;
    printk("---------%s------------\n",__FUNCTION__);
    //区分按键 ----按下 还是 松开

    //获取当前触发中断的按键信息
    p = (struct buttons*)arg;

    value = gpio_get_value(p->gpio);
    if(value){
        //松开
        printk("kernel: %s up!\n",p->name);
        button_dev->event.code = p->code;
        button_dev->event.value = 0;
    }else{
        //按下
        printk("kernel: %s pressed!\n",p->name);
        button_dev->event.code = p->code;
        button_dev->event.value = 1;
    }

    //在中断处理函数中 将tasklet对象加入到内核线程中
    tasklet_schedule(&button_dev->tasklet);

    return IRQ_HANDLED;
}

int button_drv_open(struct inode *inode , struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}

ssize_t button_drv_read(struct file *filp, char __user * buf, size_t size , loff_t * flags)
{
    int ret;
    printk("---------%s------------\n",__FUNCTION__);
#if 1
    // 如果没有数据,则结束 ,如果有数据,则copy_to_user返回数据给应用空间
    if((filp->f_flags & O_NONBLOCK) && !button_dev->have_data)
        return -EAGAIN;
#endif
    //判断是否有资源可读
    wait_event_interruptible(button_dev->wq_head, button_dev->have_data);

    ret = copy_to_user(buf, &button_dev->event, size);
    if(ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }

    //清空event,同时将have_data赋0
    memset(&button_dev->event, 0,sizeof(button_dev->event));
    button_dev->have_data = 0;
    return size;
}

ssize_t button_drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *flag)
{

    printk("---------%s------------\n",__FUNCTION__);
    return size;
}

long button_drv_ioctl(struct file *filp , unsigned int cmd , unsigned long args)
{
    int ret;
    void __user *argp;
    struct mem_data data;
    printk("---------%s------------\n",__FUNCTION__);

    argp = (void __user *)args;
    switch(cmd){
        case BUTTON_IOC_GET_DATA:
            memset(&data.buf,0,sizeof(data.buf));
            memcpy(data.buf, button_dev->virt_mem, sizeof(data.buf));
            ret = copy_to_user(argp, &data, sizeof(data));
            if(ret > 0){
                return -EFAULT;
            }
            break;
        default:
            printk("unkown cmd!\n");
    }

    return 0;
}

int button_drv_mmap(struct file * filp, struct vm_area_struct * vma)
{
    unsigned int addr;
    printk("---------%s------------\n",__FUNCTION__);

    // 1, 通过申请的虚拟空间找到对应的物理空间
    addr = virt_to_phys(button_dev->virt_mem);
    memset(button_dev->virt_mem,0,sizeof(button_dev->virt_mem));
    // 2,实现映射
    vma->vm_flags |= VM_IO;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
                    PAGE_SIZE, vma->vm_page_prot)) {
        printk(KERN_ERR "%s: io_remap_pfn_range failed\n",
            __func__);
        return -EAGAIN;
    }

    return 0;
}

unsigned int button_drv_poll(struct file * filp, struct poll_table_struct * pts)
{
    int mask = 0; 
    printk("---------%s------------\n",__FUNCTION__);

    // 1,将等待队列头注册到系统中----VFS中
    poll_wait(filp,&button_dev->wq_head, pts);

    // 2,如果产生了按键中断-有数据可读,返回:POLLIN,没有数据返回:0
    if(button_dev->have_data)
        mask |= POLLIN;

        return mask;
}


int button_drv_close(struct inode *inode, struct file *filp)
{
    printk("---------%s------------\n",__FUNCTION__);
    return 0;
}
struct file_operations fops = {
    .open = button_drv_open,
    .read = button_drv_read,
    .write = button_drv_write,
    .unlocked_ioctl = button_drv_ioctl,
    .poll = button_drv_poll,
    .mmap = button_drv_mmap,
    .release = button_drv_close,
};
static int __init button_init(void)
{
    int ret,i;
    printk("---------%s------------\n",__FUNCTION__);
    // 0, 初始化对象
    button_dev = kzalloc(sizeof(struct s5pv210_button), GFP_KERNEL);
    if(IS_ERR(button_dev)){
        printk("kzalloc error\n");
        return  -ENOMEM;
    }
    // 1,申请设备号 
    #if 0
    // 静态申请设备号
    button_dev->devno = MKDEV(256, 0);
    ret = register_chrdev_region(button_dev->devno, 1 , "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #else
    // 动态申请设备号
    ret = alloc_chrdev_region(&button_dev->devno, 10, 1, "button_drv");
    if(ret< 0){
        printk("register_chrdev error!\n");
        ret = -EINVAL;
        goto err_free;
    }
    #endif
    //创建cdev
        // 1> 申请cdev的空间-----实例化cdev
            button_dev->cdev = cdev_alloc();
            if(IS_ERR(button_dev->cdev)){
                printk("cdev_alloc error\n");
                ret = PTR_ERR(button_dev->cdev);
                goto err_unregister;
            }
        // 2> 初始化cdev
        cdev_init(button_dev->cdev, &fops);

        // 3> 将cdev注册到内核中
        ret = cdev_add(button_dev->cdev, button_dev->devno, 1);

    // 2,创建设备文件

    button_dev->cls= class_create(THIS_MODULE,"button_class");
    if(IS_ERR(button_dev->cls)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->cls);
        goto err_unregister;
    }

    button_dev->dev= device_create(button_dev->cls, NULL, button_dev->devno, NULL, "button%d",5);
    if(IS_ERR(button_dev->dev)){
        printk("class_create error\n");
        ret = PTR_ERR(button_dev->dev);
        goto err_class;
    }

    // 3,硬件初始化----中断申请
    for( i = 0; i <ARRAY_SIZE(buttons_set); i++){
        ret = request_irq(buttons_set[i].irqno, button_irq_handler,buttons_set[i].flags,buttons_set[i].name, &buttons_set[i]);
        if(ret < 0){
            printk("request_irq error\n");
            goto err_device;
        }
    }
    //初始化等待队列头
    init_waitqueue_head(&button_dev->wq_head);

    //申请一块虚拟空间
    button_dev->virt_mem = kzalloc(PAGE_SIZE, GFP_KERNEL);
    if(IS_ERR(button_dev->virt_mem)){
        printk("kzalloc error\n");
        ret = PTR_ERR(button_dev->virt_mem);
        goto err_class;
    }

    //中断下半部的初始化 : tasklet的初始化
    tasklet_init(&button_dev->tasklet, button_irq_tasklet,12300);

    return 0;
err_device:
    device_destroy(button_dev->cls, button_dev->devno);
err_class:
    class_destroy(button_dev->cls);
err_unregister:
    unregister_chrdev_region(button_dev->devno, 1);
err_free:
    kfree(button_dev);
    return ret;
}

static void __exit button_exit(void)
{
    int i;
    printk("---------%s------------\n",__FUNCTION__);
    for( i = 0; i <ARRAY_SIZE(buttons_set); i++)
        free_irq(buttons_set[i].irqno, &buttons_set[i]);
    device_destroy(button_dev->cls, button_dev->devno);
    class_destroy(button_dev->cls);
    unregister_chrdev_region(button_dev->devno, 1);
    kfree(button_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");   //认证









Makefile文件代码


KERNEL_DIR = /home/lpf/1803/s5pv210/kernel/linux-3.0.8    #指定内核源码路径
CUR_DIR = $(shell pwd)     
SRC = test.c
MYAPP = test

all:
    #使make进入内核源码目录,并将当前目录下的源码作为内核的模块一起编译
    make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
    arm-none-linux-gnueabi-gcc -o $(MYAPP) $(SRC)

clean:
    #删除上面生成的文件
    make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
    rm $(MYAPP) .*.sw?
install:
    cp *.ko $(MYAPP) /opt/rootfs/drv_module

obj-m = button_drv.o

test.c应用层代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <poll.h>
#include <sys/mman.h>

#define PAGE_SHIFT            12
#define PAGE_SIZE   (1UL << PAGE_SHIFT)

//自定义一个按键的数据包
struct button_event{
    int code;   // 按键的名称 -----键值:ESC ENTER UP
    int value;  // 按键的状态 : 按下---1 ,松开----0
};

#define BUTTON_IOC_GET_DATA  _IO('I', 0x1234)
struct mem_data{
    char buf[128];
};

int main(void)
{
    int fd;
    int ret,on;
    struct button_event event;
    char buf[100];

    fd = open("/dev/button5",O_RDWR);
    if(fd < 0){
    printf("open");
    exit(1);
    }

    //测试mmap的功能
    char str[128];
    struct mem_data data;
    char * addr = mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == NULL){
    perror("mmap");
    exit(1);
    }
    //向映射的物理空间中写入一个字符串测试
    printf("请输入一个字符串:");
    memset(str,0,128);
    fgets(str,128,stdin);
    str[strlen(str)-1] = '\0';
    memcpy(addr,str,strlen(str));
    sleep(1);

    //测试数据有没有真正的写入到物理空间
    memset(&data,0,sizeof(data));
    ret = ioctl(fd,BUTTON_IOC_GET_DATA,&data);
    if(ret < 0){
    perror("ioctl");
    exit(1);
    }
    printf("data.buf = %s\n",data.buf);
    sleep(1);

    //用poll实现同时监控标准输入和按键
    struct pollfd pfds[2];

    pfds[0].fd = STDIN_FILENO;      //监控标准输入
    pfds[0].events = POLLIN;        //是否可读

    pfds[1].fd = fd;        //按键
    pfds[1].events = POLLIN;        //是否可读

    while(1){
    ret = poll(pfds,2,-1);
    if(ret < 0){
        perror("poll");
        exit(1);
    }
    if(ret > 0){
        //判断哪个文件描述符上有数据可读
        if(pfds[0].revents && POLLIN){   //标准输入可读
        fgets(buf,100,stdin);
        printf("%s",buf);
        }
        if(pfds[1].revents && POLLIN){  //按键触发了中断
        memset(&event,0,sizeof(event));
        ret = read(fd,&event,sizeof(event));
        if(ret  < 0){
            perror("read");
            exit(1);
        }
        switch(event.code){
            case KEY_UP:
            if(event.value){  //按下
                printf("上键----按下!\n");
            }else{  //松开
                printf("上键----松开!\n");
            }
            break;
            case KEY_DOWN:
            if(event.value){  //按下
                printf("下键----按下!\n");
            }else{  //松开
                printf("下键----松开!\n");
            }
            break;
            case KEY_LEFT:
            if(event.value){  //按下
                printf("左键----按下!\n");
            }else{  //松开
                printf("左键----松开!\n");
            }
            break;
            case KEY_RIGHT:
            if(event.value){  //按下
                printf("右键----按下!\n");
            }else{  //松开
                printf("右键----松开!\n");
            }
            break;
        }
        }
        sleep(1);
    }
    }


    close(fd);
    return 0;
}

linux驱动poll机制 mmap的实现 中断下半部的实现

相关标签: 嵌入式