• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

嵌入式开发记录-补充02:IO模型


Recommended Posts

1、阻塞:参考:https://www。搜狐。com/a/258717832 _ 781584

当条件不满足的时候,应用进程睡眠;

struct __wait_queue {

无符号整数标志;

无效*私有;

等待_队列_函数_t函数;

结构列表_头任务列表;

};

typedef struct _ _ wait _ queue wait _ queue _ t;

1、初始化等待队列头,每个等待队列都有一个头;

init _ wait queue _ head((等待队列_ head _ t * q)

2、初始化等待队列项,对应每个任务。

在阅读()函数中初始化等待队列项,在条件不满足的时候需要应用层睡眠,这里是应用层调用,因此目前的指到应用层;

void init _ wait队列条目(wait _ queue _ t * q,struct task_struct *p ) //当前

3、添加等待队列项到等待队列头的链表上;

void add _ wait _ queue(wait _ queue _ head _ t * q,wait _ queue _ t * wait);

4、设置当前进程的状态

设置当前状态(状态值)//设置应用层的状态

#define TASK_INTERRUPTIBLE 1

#定义任务_不间断2

5、进程调度

无效计划(无效)//进程调度:进入睡眠状态,如果条件满足并唤醒,重新返回到这里运行。

//- 条件满足唤醒-

唤醒(等待队列头t *q)

wake _ up _ interruptible(wait _ queue _ head _ t * x)

6、从等待队列上移除等待队列项

void remove _ wait _ queue(wait _ queue _ head _ t * q,wait _ queue _ t * wait);

7、设置应用层状态-运行态

设置当前状态(状态值)

阻塞超正析象管(图片Orthicon)实现方式一:

ContractedBlock.gif

ExpandedBlockStart.gif

#包含linux/init.h

#包含linux/kernel.h

#包含linux/module.h

#包含linux/fs.h

#包含linux/cdev.h

#包含Linux/设备. h

#包含linux/err.h

#include asm/uaccess.h //适用于x86平台。

//#包含ASM-generic/u访问。h//使用手臂武器袖子装备平台。

#包含linux/wait.h

#包含linux/sched.h

#定义计数3

#定义开发名称'开发演示'

静态dev _ t devNum=0;

静态结构cdev * mydev=NULL

静态结构类*开发_ cls=空

静态结构设备* devp=NULL

static char kbuf[128]={ ' \ 0 ' };

静态int kbufcount=0;

静态int ma=240

静态等待_队列_头_ t wqh

静态等待队列队列

静态int my open(struct inode * node,struct

file *filp) { printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__); return 0; } static ssize_t myread (struct file *filp, char __user *ubuf, size_t size, loff_t *off) { printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__); //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size); if(kbufcount == 0){ init_waitqueue_entry(&wq, current); // 2.2 初始化等待队列项。 add_wait_queue(&wqh, &wq); // 2.3 等待队列项添加到等待队列头后面。 set_current_state(TASK_INTERRUPTIBLE); // 2.4 设置当前进程状态 schedule(); // 上层应用进程将阻塞在这里 // 2.5 进程调度---进入睡眠,条件满足并返回 set_current_state(TASK_RUNNING); // 2.7 设置当前进程状态 remove_wait_queue(&wqh, &wq); // 2.8 将等待队列项移除 } if(size > kbufcount) size = kbufcount; if(copy_to_user(ubuf, kbuf,13)){ printk(KERN_INFO "copy to user failed\n"); return -EAGAIN; } kbufcount =0; return 0; } static ssize_t mywrite (struct file *filp, const char __user * ubuf, size_t size, loff_t *off) { //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size); if(size > 128){ size = 128; } if(copy_from_user(kbuf, ubuf, size)){ printk(KERN_ERR "copy from user failed\n"); return -EAGAIN; // 拷贝失败应用层程序再尝试一次; } kbufcount = size; wake_up(&wqh); // 2.6 唤醒队列头后面所有进程; printk(KERN_INFO "write buf:%s\n", kbuf); printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__); return kbufcount; } static int myrelaese (struct inode *nod, struct file *file) { printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__); return 0; } static struct file_operations fileops={ .owner = THIS_MODULE, .open = myopen, .release = myrelaese, .read = myread, .write = mywrite, }; static int __init mdev_init(void) { int ret=0,i; printk("Hello cdev\n"); //ret = alloc_chrdev_region(&devNum, 0, 1, "dev_demo"); devNum = MKDEV(ma,0); printk("dev major:%d.\n", MAJOR(devNum)); printk("dev manor:%d.\n", MINOR(devNum)); ret = register_chrdev_region(devNum, COUNT, DEV_NAME); if(ret != 0){ printk("chr dev region err\n"); goto err0; } // 1. 为cdev分配空间. mydev = cdev_alloc(); if(mydev == NULL){ printk(KERN_ERR "alloc cdev error\n"); goto err1; } // 2. cdev初始化. cdev_init(mydev, &fileops); // 3. 添加cdev ret = cdev_add(mydev, devNum, COUNT); if(ret < 0 ){ printk(KERN_ERR "dev add error\n"); goto err1; } // 4. 在sys/class下创建目录 dev_cls = class_create(THIS_MODULE, DEV_NAME); if(IS_ERR(dev_cls)){ printk(KERN_ERR "class create failed:%ld\n", PTR_ERR(dev_cls)); ret = PTR_ERR(dev_cls); goto err2; } // 5、创建设备结点,在dev目录下,创建名称为DEV_NAME+i的设备结点; for(i=0;i<3;i++){ devp = device_create(dev_cls, NULL, MKDEV(ma,i), NULL, "%s%d", DEV_NAME,i); if(IS_ERR(devp)){ printk(KERN_ERR "device create failed: %ld\n", PTR_ERR(devp)); ret = PTR_ERR(devp); goto err3; } } //2.1 初始化等待队列头。 init_waitqueue_head(&wqh); // 初始化等待队列头。 printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__); return 0; err3: for(--i;i>=0;i--){ device_destroy(dev_cls,MKDEV(ma,i)); } class_destroy(dev_cls); err2: cdev_del(mydev); err1: unregister_chrdev_region(devNum, COUNT); err0: return 0; } static void __exit mdev_exit(void) { int i=0; printk("Good bye\n"); for(i=0;i<3;i++){ device_destroy(dev_cls,MKDEV(ma,i)); } class_destroy(dev_cls); cdev_del(mydev); unregister_chrdev_region(devNum, COUNT); } module_init(mdev_init); module_exit(mdev_exit); MODULE_LICENSE("GPL"); View Code

  阻塞IO实现方式二:

    wait_event(wait_queue_head_t *wq, condition)
    wait_event_interruptible(wait_queue_head_t *wq, condition)     // 可中断的
    wake_up(wait_queue_head_t *q) 
    wake_up_interruptible(wait_queue_head_t * x)

2、非阻塞:
  当条件不满足的时候,返回错误给用进程;

  应用层的open操作默认为阻塞操作,那么如果需要非阻塞需要应用层传递非阻塞标志;flags中的O_NONBLOCK

  在驱动中:

if(filp->f_flags & O_NONBLOCK){
    return -EAGAIN;
}

3、IO多路复用:

  操作多个文件,对应多个文件描述符;每个文件的读写操作是否阻塞需要判断,如果有其中的几个文件读写操作不阻塞了,就是可以操作了,通过返回值返回这样的信息;

  其中对应的驱动的操作接口为:应用层调用select、poll、epoll都会调到下面这个接口;

unsigned int (*poll) (struct file *, struct poll_table_struct *);

  驱动中需要实现的,主要实现里面那个参数

static unsigned int mypoll (struct file *filp, struct poll_table_struct *pt)
{
    int mask =0;
    if(kbufcount){
        mask |= POLLIN;
    }
    // static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    poll_wait(filp, &wqh,pt);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return mask;
}
ContractedBlock.gifExpandedBlockStart.gif
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <asm/uaccess.h>   // 适用于x86平台。
//#include <asm-generic/uaccess.h>  // 使用ARM平台。
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#define COUNT 3
#define DEV_NAME "devDemo"
static dev_t  devNum=0;
static     struct cdev* mydev=NULL;
static struct class * dev_cls=NULL;
static struct device* devp=NULL;
static char kbuf[128]={'\0'};
static int kbufcount=0;
static int ma=240;
static wait_queue_head_t     wqh;
static wait_queue_t         wq;
static int myopen (struct inode *node, struct file *filp)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return 0;
}
static ssize_t myread (struct file *filp, char __user *ubuf, size_t size, loff_t *off)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size);
    if(kbufcount == 0){
        if(filp->f_flags & O_NONBLOCK){
            return -EAGAIN;
        }
        init_waitqueue_entry(&wq, current);      // 2.2 初始化等待队列项。
        add_wait_queue(&wqh, &wq);                // 2.3 等待队列项添加到等待队列头后面。
        set_current_state(TASK_INTERRUPTIBLE);    // 2.4 设置当前进程状态
        schedule();    // 上层应用进程将阻塞在这里    // 2.5 进程调度---进入睡眠,条件满足并返回
        set_current_state(TASK_RUNNING);        // 2.7 设置当前进程状态
        remove_wait_queue(&wqh, &wq);            // 2.8 将等待队列项移除
    }
    if(size > kbufcount)
        size = kbufcount;
    if(copy_to_user(ubuf, kbuf,13)){
        printk(KERN_INFO "copy to user failed\n");
        return -EAGAIN;
    }
    kbufcount =0;
    return 0;
}
static ssize_t mywrite (struct file *filp, const char __user * ubuf, size_t size, loff_t *off)
{
    //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size);
    
    if(size > 128){
        size = 128;
    }
    if(copy_from_user(kbuf, ubuf, size)){
        printk(KERN_ERR "copy from user failed\n");
        return -EAGAIN;   // 拷贝失败应用层程序再尝试一次;
    }
    kbufcount = size;
    wake_up(&wqh);  // 2.6 唤醒队列头后面所有进程;
    printk(KERN_INFO "write buf:%s\n", kbuf);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    
    return kbufcount;
}
static int myrelaese (struct inode *nod, struct file *file)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return 0;
}
static unsigned int mypoll (struct file *filp, struct poll_table_struct *pt)
{
    int mask =0;
    if(kbufcount){
        mask |= POLLIN;
    }
    // static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    poll_wait(filp, &wqh,pt);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return mask;
}
static struct file_operations  fileops={
    .owner     = THIS_MODULE,
    .open     = myopen,
    .release    = myrelaese,
    .read    = myread,
    .write    = mywrite,
    .poll    = mypoll,
    
};
static int __init mdev_init(void)
{
    int ret=0,i;
    printk("Hello cdev\n");
    //ret = alloc_chrdev_region(&devNum, 0, 1, "dev_demo");
    devNum = MKDEV(ma,0);
    printk("dev major:%d.\n", MAJOR(devNum));
    printk("dev manor:%d.\n", MINOR(devNum));
    
    ret = register_chrdev_region(devNum, COUNT, DEV_NAME);
    if(ret != 0){
        printk("chr dev region err\n");
        goto err0;
    }
    // 1. 为cdev分配空间.
    mydev = cdev_alloc();
    if(mydev == NULL){
        printk(KERN_ERR "alloc cdev error\n");
        goto err1;
    }
    // 2. cdev初始化.
    cdev_init(mydev, &fileops);
    // 3. 添加cdev
    ret = cdev_add(mydev, devNum, COUNT);
    if(ret < 0 ){
        printk(KERN_ERR "dev add error\n");
        goto err1;
    }
    // 4. 在sys/class下创建目录
    dev_cls = class_create(THIS_MODULE, DEV_NAME);
    if(IS_ERR(dev_cls)){
        printk(KERN_ERR "class create failed:%ld\n", PTR_ERR(dev_cls));
        ret = PTR_ERR(dev_cls);
        goto err2;
    }
    // 5、创建设备结点,在dev目录下,创建名称为DEV_NAME+i的设备结点;
    for(i=0;i<3;i++){
        devp = device_create(dev_cls, NULL, MKDEV(ma,i), NULL, "%s%d", DEV_NAME,i);
        if(IS_ERR(devp)){
            printk(KERN_ERR "device create failed: %ld\n", PTR_ERR(devp));
            ret = PTR_ERR(devp);
            goto err3;
        }
    }
    //2.1 初始化等待队列头。
    init_waitqueue_head(&wqh);  // 初始化等待队列头。
    
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    
    return 0;
    
err3:
    for(--i;i>=0;i--){
        device_destroy(dev_cls,MKDEV(ma,i));
    }
    class_destroy(dev_cls);
err2:
    cdev_del(mydev);
    
err1:
    unregister_chrdev_region(devNum, COUNT);
err0:
    return 0;
}
static void __exit mdev_exit(void)
{
    int i=0;
    printk("Good bye\n");
    for(i=0;i<3;i++){
        device_destroy(dev_cls,MKDEV(ma,i));
    }
    class_destroy(dev_cls);
    cdev_del(mydev);
    unregister_chrdev_region(devNum, COUNT);
}
module_init(mdev_init);
module_exit(mdev_exit);
MODULE_LICENSE("GPL");
View Code

  测试应用实例:

ContractedBlock.gifExpandedBlockStart.gif
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
char rBuf[64];
int main(int argc, char* argv[])
{
    int fd =0,ret=0;
    fd_set rfds;
    printf("hello world\n");
    struct  UData dat={100,"liunx"};
    fd = open("/dev/devDemo1",O_RDWR);
    if(fd < 0){
        printf("open device failed.\n");
        perror("open");
        return -1;
    }
    while(1){
        FD_ZERO(&rfds);
        FD_SET(fd, &rfds);
        ret = select(fd+1,&rfds,  NULL, NULL, NULL);
        if(ret < 0){
            perror("select\n");
        }
        if(FD_ISSET(fd, &rfds)){
            read(fd,rBuf, 64);
            printf("select read:%s\n", rBuf);
        }
    }
    
    close(fd);
    return 0;
}
View Code

 

4、异步通知:

  基于通知机制---->信号的方式signal

  内核(驱动)--通知-->应用层进程
  内核发送SIGIO信号,那么应用程序异步接收信号,使用异步接收信号
  开启异步接收

fcntl(fd, F_SETFL, fcntl(fd,F_GETFL)|O_ASYNC);   

  设置异步接收拥有者:

fcntl(fd, F_SETOWN, getpid())

  内核实现异步通知:
    fcntl系统调用:do_fcntl
  do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file * filp)
    调用:int (*fasync) (int, struct file *, int);
  在该接口调用:fasync_helper

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

  初始化异步通知结构体,并插入异步通知队列中;如果条件满足了,发送信号到应用程序,应用程序接收信号,进行读操作;

static unsigned int mypoll (struct file *filp, struct poll_table_struct *pt)
{
    int mask =0;
    if(kbufcount){
        mask |= POLLIN;
    }
    // static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    poll_wait(filp, &wqh,pt);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return mask;
}

  内核给应用程序发送信号:

// void kill_fasync(struct fasync_struct **fp, int sig, int band)
kill_fasync(&fapp, SIGIO, POLL_IN);  // 发送信号
ContractedBlock.gifExpandedBlockStart.gif
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <asm/uaccess.h>   // 适用于x86平台。
//#include <asm-generic/uaccess.h>  // 使用ARM平台。
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/signal.h>
#define COUNT 3
#define DEV_NAME "devDemo"
static dev_t  devNum=0;
static     struct cdev* mydev=NULL;
static struct class * dev_cls=NULL;
static struct device* devp=NULL;
static char kbuf[128]={'\0'};
static int kbufcount=0;
static int ma=240;
static wait_queue_head_t     wqh;
static wait_queue_t         wq;
static struct fasync_struct * fapp=NULL;
static int myopen (struct inode *node, struct file *filp)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return 0;
}
static ssize_t myread (struct file *filp, char __user *ubuf, size_t size, loff_t *off)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size);
    if(kbufcount == 0){
        if(filp->f_flags & O_NONBLOCK){
            return -EAGAIN;
        }
        init_waitqueue_entry(&wq, current);      // 2.2 初始化等待队列项。
        add_wait_queue(&wqh, &wq);                // 2.3 等待队列项添加到等待队列头后面。
        set_current_state(TASK_INTERRUPTIBLE);    // 2.4 设置当前进程状态
        schedule();    // 上层应用进程将阻塞在这里    // 2.5 进程调度---进入睡眠,条件满足并返回
        set_current_state(TASK_RUNNING);        // 2.7 设置当前进程状态
        remove_wait_queue(&wqh, &wq);            // 2.8 将等待队列项移除
    }
    if(size > kbufcount)
        size = kbufcount;
    if(copy_to_user(ubuf, kbuf,13)){
        printk(KERN_INFO "copy to user failed\n");
        return -EAGAIN;
    }
    kbufcount =0;
    return 0;
}
static ssize_t mywrite (struct file *filp, const char __user * ubuf, size_t size, loff_t *off)
{
    //printk(KERN_INFO "buf addr:0x%x, szie:%d\n", ubuf, size);
    
    if(size > 128){
        size = 128;
    }
    if(copy_from_user(kbuf, ubuf, size)){
        printk(KERN_ERR "copy from user failed\n");
        return -EAGAIN;   // 拷贝失败应用层程序再尝试一次;
    }
    kbufcount = size;
    //wake_up(&wqh);  // 2.6 唤醒队列头后面所有进程;
    kill_fasync(&fapp, SIGIO, POLL_IN);  // 发送信号
    printk(KERN_INFO "write buf:%s\n", kbuf);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    
    return kbufcount;
}
static int myrelaese (struct inode *nod, struct file *file)
{
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return 0;
}
static unsigned int mypoll (struct file *filp, struct poll_table_struct *pt)
{
    int mask =0;
    if(kbufcount){
        mask |= POLLIN;
    }
    // static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    poll_wait(filp, &wqh,pt);
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return mask;
}
// 异步IO
/*
int (*fasync) (int, struct file *, int);
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
内核中发信号:                                                // POLL_IN/ POLL_OUT
    kill_fasync(struct fasync_struct ** fp, int sig, int band)
*/
    
static int myfasync(int fd, struct file * filp, int isAsync)
{
    // 1、
    
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    return fasync_helper(fd, filp, isAsync, &fapp);;
}
static struct file_operations  fileops={
    .owner     = THIS_MODULE,
    .open     = myopen,
    .release    = myrelaese,
    .read    = myread,
    .write    = mywrite,
    .poll    = mypoll,
    .fasync    = myfasync,
    
};
static int __init mdev_init(void)
{
    int ret=0,i;
    printk("Hello cdev\n");
    //ret = alloc_chrdev_region(&devNum, 0, 1, "dev_demo");
    devNum = MKDEV(ma,0);
    printk("dev major:%d.\n", MAJOR(devNum));
    printk("dev manor:%d.\n", MINOR(devNum));
    
    ret = register_chrdev_region(devNum, COUNT, DEV_NAME);
    if(ret != 0){
        printk("chr dev region err\n");
        goto err0;
    }
    // 1. 为cdev分配空间.
    mydev = cdev_alloc();
    if(mydev == NULL){
        printk(KERN_ERR "alloc cdev error\n");
        goto err1;
    }
    // 2. cdev初始化.
    cdev_init(mydev, &fileops);
    // 3. 添加cdev
    ret = cdev_add(mydev, devNum, COUNT);
    if(ret < 0 ){
        printk(KERN_ERR "dev add error\n");
        goto err1;
    }
    // 4. 在sys/class下创建目录
    dev_cls = class_create(THIS_MODULE, DEV_NAME);
    if(IS_ERR(dev_cls)){
        printk(KERN_ERR "class create failed:%ld\n", PTR_ERR(dev_cls));
        ret = PTR_ERR(dev_cls);
        goto err2;
    }
    // 5、创建设备结点,在dev目录下,创建名称为DEV_NAME+i的设备结点;
    for(i=0;i<3;i++){
        devp = device_create(dev_cls, NULL, MKDEV(ma,i), NULL, "%s%d", DEV_NAME,i);
        if(IS_ERR(devp)){
            printk(KERN_ERR "device create failed: %ld\n", PTR_ERR(devp));
            ret = PTR_ERR(devp);
            goto err3;
        }
    }
    //2.1 初始化等待队列头。
    init_waitqueue_head(&wqh);  // 初始化等待队列头。
    
    printk(KERN_INFO "--%s--%s--%d----\n", __FILE__,__func__, __LINE__);
    
    return 0;
    
err3:
    for(--i;i>=0;i--){
        device_destroy(dev_cls,MKDEV(ma,i));
    }
    class_destroy(dev_cls);
err2:
    cdev_del(mydev);
    
err1:
    unregister_chrdev_region(devNum, COUNT);
err0:
    return 0;
}
static void __exit mdev_exit(void)
{
    int i=0;
    printk("Good bye\n");
    for(i=0;i<3;i++){
        device_destroy(dev_cls,MKDEV(ma,i));
    }
    class_destroy(dev_cls);
    cdev_del(mydev);
    unregister_chrdev_region(devNum, COUNT);
}
module_init(mdev_init);
module_exit(mdev_exit);
MODULE_LICENSE("GPL");
View Code

 

Link to comment
Share on other sites