1 结构体说明:

    struct cdev {

        struct kobject kobj;          // 每一个 cdev 都是一个 kobject

        struct module *owner;       // 指向实现驱动的模块

        const struct file_operations *ops;   // 操纵这个字符设备文件的方法

        struct list_head list;       // 与 cdev 相应的字符设备文件的 inode->i_devices 的链表头

        dev_t dev;                   // 起始设备编号

        unsigned int count;       // 设备范围号大小

    };

    内核中每一个字符设备都相应一个 cdev 结构的变量。





    struct file_operations {

        struct module *owner;

        loff_t (*llseek) (struct file *, loff_t, int);

        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

        int (*readdir) (struct file *, void *, filldir_t);

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

        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

        int (*mmap) (struct file *, struct vm_area_struct *);

        int (*open) (struct inode *, struct file *);

        int (*flush) (struct file *, fl_owner_t id);

        int (*release) (struct inode *, struct file *);

        int (*fsync) (struct file *, struct dentry *, int datasync);

        int (*aio_fsync) (struct kiocb *, int datasync);

        int (*fasync) (int, struct file *, int);

        int (*lock) (struct file *, int, struct file_lock *);

        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, 

                                 unsigned   long, unsigned long);

        int (*check_flags)(int);

        int (*flock) (struct file *, int, struct file_lock *);

        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, 

                          unsigned int);

        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,

                        unsigned int);

        int (*setlease)(struct file *, long, struct file_lock **);

    };

以下是经常使用的一些方法的介绍:

     loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);

        指针參数filp为进行读取信息的目标文件结构体指针。參数 p 为文件定位的目标偏移量(依据orig指定的位置的偏移量,这个值能够为负值)。參数orig为对文件定位的起始地址,这个值能够为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2)llseek 方法用作改变文件里的当前读/写位置, 

        该方法运行成功返回新位置,返回值小于0,表示运行失败。

        这种方法相应应用程序中的seek函数。





     ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);

        指针參数 filp 为进行读取信息的目标文件,指针參数buffer 为相应放置信息的缓冲区(即用户空间内存地址),參数size为要读取的数据的长度,參数 p 为读的位置相对于文件开头的偏移。在读取信息后。这个指针一般都会移动,移动的值为要读取信息的长度值

        该方法运行成功返回实际读取的字节数。返回负值表示运行失败。

        该方法相应应用程序中的read函数。

最后需要注意的是buffer是用户空间的地址,所以不能用memcpy进行拷贝,必需要使用copy_to_user来进行内核空间到用户空间的拷贝。示比例如以下

        copy_to_user(buffer ,ptr ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。

   

    ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);

        參数filp为目标文件结构体指针。buffer为要写入文件的信息缓冲区。count为要写入信息的长度,ppos为当前的偏移位置,这个值一般是用来推断写文件是否越界

        函数运行成功返回实际发送的字节数。失败返回负值。

        该方法相应应用程序中的write函数。

        同read一样buffer是用户空间的地址,所以不能直接拷贝,须要用copy_from_user来进行拷贝。示比例如以下:

        copy_from_user(ptr , buffer ,len) ; buffer为用户空间的地址,ptr为内核空间的地址,len为要拷贝的长度。

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

        这是一个设备驱动中的轮询函数,第一个參数为file结构指针,第二个为轮询表指针.

        这个函数资源可用时返回设备资源的可获取状态,即POLLIN,POLLOUT。POLLPRI。POLLERR,POLLNVAL等宏的位“或”结果。否则返回0 。

该函数具体的使用方法能够參考《select poll epoll使用演示样例》里面的介绍。

int (*unlocked_ioctl) ( struct file *filp, unsigned int cmd, unsigned long arg);

        注意在2.6.36以后ioctl已经不再存在了。所以这里我们仅仅说明unlocked_ioctl。

unlocked_ioctl和ioctl的差别在于ioctl多了一个struct inode *的參数。这个在移植老代码的时候要注意改动,否则编译只是的,unlocked_ioctl和ioctl相应的都是应用程序中的ioctl函数。对于应用,不论是unlocked_ioctl还是ioctl都是不须要改动的。

        unlocked_ioctl主要用于对硬件设备的控制。第一个參数为file结构指针。 第二个參数cmd是用户传进来的操作码,驱动依据这个数据来决定运行什么操作。

第三个參数arg是參数,这个參数能够是一个整数。或者是一个指针。假设是指针的话。在内核空间要通过 copy_to_user或者 copy_from_user拷贝数据。

        以下具体说明下cmd。cmd由4部分组成,设备类型(幻数),方向,序号。数据大小,linux提供了以下的宏进行操作

        _IO(type,nr)  

        _IOR(type,nr,size)  

        _IOW(type,nr,size)  

        _IOWR(type,nr,size) 

        还有以下的宏能够获取一个cmd设备类型(幻数),方向,序号。数据大小

        _IOC_DIR(nr)  

        _IOC_TYPE(nr) 

        _IOC_NR(nr)  

        _IOC_SIZE(nr)

        注意:对于我们定义的mcd。最好用依据方向、数据大小等用_IO、_IOR、_IOW、_IOWR来定义,而不要直接用一个数值。比如1、2、3这些数据。

        直接定义一个常数在早期的linux不会出错,可是在3.0以后。可能会造成某些cmd根本不会运行,可是返回的还是0。

比如我以前移植2.6.2的驱动到3.0下,结果该驱动有一个cmd定义的是2,结果之前没有问题。到3.0 应用调用ioctl返回0,没有报错,可是看串口信息,驱动中的unlocked_ioctl根本没有调用,将这个cmd用_IOR又一次定义运行就正常了,

        unlocked_ioctl运行成功返回0 ,失败返回负值。

        

    int (*mmap) (struct file *, struct vm_area_struct *);

        mmap 用来请求将设备内存映射到进程的地址空间.这个函数相应的是应用程序中的mmap.

        函数运行成功返回0,失败返回负值。

以下是演示样例代码:

    static int test_mmap( struct file *filp ,struct vm_area_struct *vma)

    {

                unsigned long size ;





                size = vma->vm_end-vma->vm_start;

                //这里的MAX_SIZE 是我们分配的用于映射的内存的大小,是我们在驱动中定义的

                //vm_pgoff是页偏移。也就是说假设应用中设置的偏移是4096(linux一页时4096字节) 那么驱动中vm_pgoff就是1,假设应用中是8192 那么驱动相应的就是2。

if( (vma->vm_pgoff<<PAGE_SHIFT)+size > MAX_SIZE )

                {

                        return -EAGAIN;

                }

                /*标记这段虚拟内存映射为IO区域,并阻止系统将该区域包括在进程的存放转存中*/ 

                vma->vm_flags|=VM_IO;

                /*标记这段区域不能被换出*/

                vma->vm_flags|=VM_RESERVED;

 

                if(remap_pfn_range(vma,vma->vm_start,(virt_to_phys(ptr_mem)>>PAGE_SHIFT)+vma->vm_pgoff,size,vma->vm_page_prot))

                        return -EAGAIN;

                return 0;

    }

    另外我们须要注意的是mmap映射的地址是按页对齐的,也就是低12位的地址要为0。

我们能够通过alloc_pages来分配内存用于映射。示比例如以下:

        struct page *page;

        page = alloc_pages(GFP_KERNEL,1);//alloc_pages的第二个參数是分配的页数,为2的n次方页。

        //比如 为0 表示分配1页(4096),为1表示分配两页(8192),为2表示分配4页

        if(page==NULL)

        {

                printk(KERN_ERR"alloc_pages return error\r\n");

                ptr_mem=NULL;

        }

        else

        {

                ptr_mem= page_address(page); 

                if(ptr_mem==NULL)

                {

                        printk(KERN_ERR"page_address return error\r\n");

                        free_pages((unsigned long)page, 1);

                }

        }        

        驱动卸载的时候调用

        if(ptr_mem)

        {

            free_pages(ptr_mem, 1);

        }

        释放分配的页。

        

        在应用中我们调用mmap函数获取映射地址的指针。

        mmap声明例如以下:

        void * mmap(void *addr,size_t length,int prot, int flags,int fd,off_t offset);

        该函数运行成功返回一个指针,应用能够直接对这个指针进行操作。就能够操作驱动中映射的内存了。

        參数说明例如以下:

        void *addr 是程序猿所希望的虚拟地址作为起始映射地址,通常为NULL。内核自己主动分配。

                size_t length当然是指须要映射的区域大小。

                int flags是指对这段区域的保护方式。

详细的能够參看内核源代码的linux/mm.h。

经常使用的是PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE。

                int flags主要是指对这段区域的映射方式,主要分为两种方式MAP_SHARE,MAP_PRIVATE.当中的MAP_SHARE是指对映射区域的写操作会更新到文件里,这样就相当于直接操作文件。

而MAP_PRIVATE通常採用一种称为"写时保护的机制"实现映射,对映射区的写操作不会更新到文件里,实现方法是将须要被写操作的页拷贝到又一次分配的新页中,然后再对新的页进行写操作。原来的映射并没有改变,可是读操作并不会又一次分配物理内存空间。

详细的參考深入理解计算机系统。

                int fd是指将被映射的文件描写叙述符。映射须要保证文件描写叙述符的正确性。

                off_t offset是指从文件的详细位置開始映射。通常情况下能够设置为0,即从开头映射。

这里需要注意的是这个偏移必需要页对齐的。就是说必须是4096的倍数,否则mmap将失败。这个相应的就是驱动中的vm_pgoff。

以下是应用调用的样例:

        fd1= open("/dev/filename", O_RDWR  );

        mmap_ptr = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 0);

        mmap_ptr2 = (unsigned char *)mmap(0, 4096, PROT_READ |PROT_WRITE , MAP_SHARED , fd1, 8192);

        。

。。。。

。。

        munmap(mmap_ptr ,4096);

        munmap(mmap_ptr2 ,4096);





    int (*open) (struct inode * inode , struct file *  filp ) ;

        inode 为文件节点,这个节点仅仅有一个,不管用户打开多少个文件,都仅仅是相应着一个inode结构。

        可是filp就不同。仅仅要打开一个文件。就相应着一个file结构体,file结构体通经常使用来追踪文件在执行时的状态信息,所以我们一般会在open中分配针对该设备的将结构。然后将这个结构的地址赋值给struct file的private_data。这样在read、write等其它的方法中就能够使用这个分配的结构了。

        虽然这经常是对设备文件进行的第一个操作, 不要求驱动声明一个相应的方法. 假设这个项是 NULL, 设备打开一直成功, 可是你的驱动不会得到通知.





    int (*release) (struct inode *, struct file *);

        release ()函数当最后一个打开设备的用户进程运行close()系统调用的时候。内核将调用驱动程序release()函数.release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自己定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 能够为 NULL.





    struct file {

                /*

                * fu_list becomes invalid after file_free is called and queued via

                * fu_rcuhead for RCU freeing

                */

                union {

                struct list_headfu_list;

                struct rcu_head 

                fu_rcuhead;

                } f_u;

                struct path  f_path;

                #define f_dentry f_path.dentry

                #define f_vfsmnt f_path.mnt

                const struct file_operations*f_op;

                

                /*

                * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.

                * Must not be taken from IRQ context.

                */

                spinlock_t  f_lock;

                #ifdef CONFIG_SMP

                int f_sb_list_cpu;

                #endif

                atomic_long_t  f_count;

                unsigned int f_flags;

                fmode_t  f_mode;

                loff_t  f_pos;

                struct fown_structf_owner;

                const struct cred*f_cred;

                struct file_ra_statef_ra;

                

                u64 f_version;

                #ifdef CONFIG_SECURITY

                void *f_security;

                #endif

                /* needed for tty driver, and maybe others */

                void *private_data;

                

                #ifdef CONFIG_EPOLL

                /* Used by fs/eventpoll.c to link all the hooks to this file */

                struct list_headf_ep_links;

                struct list_headf_tfile_llink;

                #endif /* #ifdef CONFIG_EPOLL */

                struct address_space*f_mapping;

                #ifdef CONFIG_DEBUG_WRITECOUNT

                unsigned long f_mnt_write_state;

                #endif

    };

    这个结构体代表一个打开的文件。系统中的每一个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建。并传递给在文件上进行操作的不论什么函数。

    以下介绍下我们须要关注的成员变量

    unsigned int  f_flags; 当打开文件时指定的标志,相应系统调用open的int flags參数。驱动程序为了支持非堵塞型操作须要检查这个标志。

    mode_t  f_mode; 对文件的读写模式,相应系统调用open的mod_t mode參数。假设驱动程序须要这个值,能够直接读取这个字段。

    void  *private_data;指向私有数据的指针。用户在open的时候能够创建自己的数据结构。用这个指针保存地址,在对设备操作的时候使用这个结构。





    struct inode {

                umode_t  i_mode;

                unsigned shorti_opflags;

                uid_t i_uid;

                gid_t i_gid;

                unsigned int  i_flags;

                

                #ifdef CONFIG_FS_POSIX_ACL

                struct posix_acl*i_acl;

                struct posix_acl*i_default_acl;

                #endif

                

                const struct inode_operations*i_op;

                struct super_block*i_sb;

                struct address_space*i_mapping;

                

                #ifdef CONFIG_SECURITY

                void *i_security;

                #endif

                

                

                /* Stat data, not accessed from path walking */

                unsigned long  i_ino;

                /*

                * Filesystems may only read i_nlink directly.  They shall use the

                * following functions for modification:

                *

                *    (set|clear|inc|drop)_nlink

                *    inode_(inc|dec)_link_count

                */

                union {

                        const unsigned int i_nlink;

                        unsigned int __i_nlink;

                };

                dev_t i_rdev;

                struct timespeci_atime;

                struct timespeci_mtime;

                struct timespeci_ctime;

                spinlock_t  i_lock; /* i_blocks, i_bytes, maybe i_size */

                unsigned short          i_bytes;

                blkcnt_t  i_blocks;

                loff_t  i_size;

                

                #ifdef __NEED_I_SIZE_ORDERED

                seqcount_t  i_size_seqcount;

                #endif

                

                /* Misc */

                unsigned long  i_state;

                struct mutex  i_mutex;

                

                unsigned long  dirtied_when; /* jiffies of first dirtying */

                

                struct hlist_nodei_hash;

                struct list_headi_wb_list;

                /* backing dev IO list */

                struct list_headi_lru;

                /* inode LRU list */

                struct list_headi_sb_list;

                union {

                        struct list_headi_dentry;

                        struct rcu_headi_rcu;

                };

                atomic_t  i_count;

                unsigned int  i_blkbits;

                u64 i_version;

                atomic_t  i_dio_count;

                atomic_t  i_writecount;

                const struct file_operations*i_fop;

                /* former ->i_op->default_file_ops */

                struct file_lock*i_flock;

                struct address_spacei_data;

                #ifdef CONFIG_QUOTA

                struct dquot  *i_dquot[MAXQUOTAS];

                #endif

                struct list_headi_devices;

                union {

                        struct pipe_inode_info*i_pipe;

                        struct block_device*i_bdev;

                        struct cdev  *i_cdev;

                };

                

                __u32 i_generation;

                

                #ifdef CONFIG_FSNOTIFY

                __u32 i_fsnotify_mask; /* all events this inode cares about */

                struct hlist_headi_fsnotify_marks;

                #endif

                

                

                #ifdef CONFIG_IMA

                atomic_t  i_readcount; /* struct files open RO */

                #endif

                void *i_private; /* fs or device private pointer */

    };

    每个设备文件相应一个inode 。struct inode 和struct file 的差别在于,比如我有一个设备/dev/mytest,那么这个/dev/mytest就相应一个struct inode,而我每次打开/dev/mytest(能够同一时候打开多次)。都会分配一个struct file。也就是一个设备文件仅仅相应一个struct inode(同一时候打开多次。也仅仅有一个),而假设这个设备文件被打开多次。那么就相应多个struct file。

    以下是我们须要关注的成员:

    dev_t i_rdev;该设备文件相应的设备号。

    struct cdev *i_cdev;该设备的cdev结构的指针。





2 相关的函数:

    int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

    该函数的功能是动态分配一个设备号,通过dev带回。

    參数说明:dev是用来带回动态分配的设备号

            firstminor第一个次设备号,通常设置为0

            count 要分配的设备数

            name 分配的设备号相应的名字,这个名字我们能够在/proc/devices里面看到。能够用cat /proc/devices查看

    返回值: 0运行成功 小于0 失败。

int register_chrdev_region(dev_t first, unsigned int count, char *name);

    该函数的功能是注冊一个(或者一组)已经定义的设备号。

參数说明:first 是要分配的起始设备编号.first 的次编号部分经常是 0。

count 是请求的连续设备编号的总数

           name 注冊的设备号相应的名字,这个名字我们能够在/proc/devices里面看到。能够用cat /proc/devices查看

    返回值: 0运行成功 小于0 失败。

    以上这两个函数功能类似,都是注冊一个或者一组设备号,可是不同的是alloc_chrdev_region是动态分配的。系统会自己主动在没有分配的主设备号中找一个可用的来分配。

而register_chrdev_region是用户自定义一个主设备号,来注冊的。调用这个函数我们必需要确保我们定义的设备号没有被使用,否则这个函数会失败。为了避免冲突和便于移植到其它平台,建议最好使用alloc_chrdev_region动态分配一个设备号。





    void unregister_chrdev_region(dev_t first, unsigned int count);

    该函数的功能是释放注冊的设备号

    參数说明:first为第一个设备号 。

              count为申请释放的设备数量





    设备号操作的相关的宏:

    MKDEV(ma,mi) 该宏的功能是依据主设备号和次设备号得到一个设备号。

ma为主设备号 ,mi为次设备号

    MAJOR(dev)   该宏的功能是获取主设备号 。dev为设备号

    MINOR(dev)   该宏的功能为获取次设备号。參数dev是设备号









    void cdev_init(struct cdev *cdev, const struct file_operations *fops);

    函数的功能是初始化一个cdev的结构。

    參数说明:struct cdev *cdev 已经定义的cdev结构的指针 ,

              const struct file_operations *fops 已经定义的file_operations 的结构的指针,该结构定义了设备文件的一系列操作。

演示样例代码例如以下:

    struct cdev my_cdev;

    cdev_init(&my_cdev, &fops);

    my_cdev.owner = THIS_MODULE;





    struct cdev *cdev_alloc(void);

    函数的功能和cdev_init类似。可是这个函数是动态分配的一个struct cdev的结构。

    返回值:分配成功返回struct cdev的结构的地址失败返回NULL。

演示样例代码例如以下:

    struct cdev *my_cdev = cdev_alloc();

    if(my_cdev)

    {

        my_cdev->ops = &fops;

        my_cdev->owner = THIS_MODULE;

    }





    int cdev_add(struct cdev *p, dev_t dev, unsigned count);

    函数的功能是将字符设备驱动程序注冊到系统中。

參数说明:struct cdev *p; 将要增加系统的cdev ,p是之前调用cdev_init或者cdev_alloc初始化好的。

     dev_t dev;已经注冊的设备号。这个是通过alloc_chrdev_region或者register_chrdev_region注冊的设备号。

              unsinged int count;要注冊的设备的数目。

返回值:运行成功返回0,失败返回负值









    void cdev_del(struct cdev *p);

    函数功能是释放之前注冊的cdev。

    函数參数:struct cdev *p,之前用cdev_add注冊的cdev的指针。





    struct class *class_create(struct module *owner, const char *name);

    函数功能是分配一个struct class的结构。

    參数说明:struct module *owner 指向模块owner的指针。通常设置为THIS_MODULE

              const char *name class的名字,这个名字能够在/sys/class/看到。系统将在/sys/class/下创建參数name指定的文件夹。

    返回值:运行成功返回struct class的地址,失败返回NULL。

    说明:实际上class_create不是一个函数而是一个宏,

    #define class_create(owner, name) \

    ({ \

                static struct lock_class_key __key;\

                __class_create(owner, name, &__key);\

    })

    只是对于我们在实际调用中,能够不须要关注的。





    void class_destroy(struct class *cls);

    函数功能是注销一个class。

    參数 cls就是我们之前用class_create分配的一个struct class的指针。

    这个函数和class_create成对使用的。





    int class_register(struct class *cls);

    函数功能和class_create类似,也是注冊一个class,实际上class_create内部也会调用__class_register。不同的是这个函数的參数是已经分配好的结构。

    參数cls是struct class的指针

    返回值运行成功返回0。失败返回负值。

实际上这个也是一个宏

    #define class_register(class) \

    ({ \

                static struct lock_class_key __key;\

                __class_register(class, &__key);\

    } )

    以下是class_register的演示样例代码:

    struct class  my_class;

    my_class->name = "myclass";

    my_class->owner = THIS_MODULE;   

    my_class->class_release = class_create_release;

    // 将class注冊到内核中,同一时候会在/sys/class/下创建class相应的节点

    int retval = class_register(my_class);





    void class_unregister(struct class *cls);

    注销一个class。这个函数和class_register成对使用。





    struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void * drvdata, const char *fmt, ...);

    函数功能是用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化。将其与函数的第一个參数所代表的逻辑类关联起来。然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。

函数可以自己主动在/sys/devices/virtual文件夹下创建新的逻辑设备文件夹。在/dev文件夹下创建于逻辑类相应的设备文件。

    參数说明:cls struct class 指针。必须在本函数调用之前先被调用class_register或者class_create注冊的。

parent 该设备的父设备的指针。假设没有就设置为NULL

              devt   该设备的设备号

     drvdata 传递给该设备的私有数据的指针,假设没有就直接用NULL

              fmt     设备名称,就是在dev文件夹下显示的设备名





    void device_destroy(struct class *cls, dev_t devt);

    函数功能:用于从linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual文件夹下相应的设备文件夹及/dev/文件夹下相应的设备文件

    參数说明:cls struct class 指针

              devt 设备号





3 以下是演示样例代码:





在头文件里定义

#define SET1 _IOR('O', 1, int)

#define SET2 _IOR('O', 2, int)

#define SET3 _IOR('O', 3, int)





在C代码中

#define TEST_DEV_NAME "mytest"

#define MAX_DEV  3

typedef struct 

{

        dev_t    test_dev;

        struct   cdev test_cdev;

        struct   class *test_class;

 

}test_DEV_struct;





typedef struct

{

        char info[100];

        int offset;

}test_data;

 

static ssize_t test_write(struct file *filp, const char __user *data,size_t len, loff_t *ppos)

{

        test_data * ptr = filp->private_data;

        if(copy_from_user(ptr->info  ,data ,len) == 0 )

        {

                ptr->offset=0;

                return len;

        }

        else

        {

                return 0;

        }

}





static ssize_t test_read(struct file *filp, char __user *data, size_t len, loff_t *ppos)

{

        test_data * ptr = filp->private_data;

        

        int length =strlen( ptr->info);

        if(length<=ptr->offset)

        {

                return 0;

        }

        if(ptr->offset+len>length)

        {

                len = length-ptr->offset;

        }

         

        copy_to_user(data ,ptr->info+ptr->offset ,len+1) ;

        ptr->offset += len;

        

        return len;

}

static loff_t test_seek(struct file *filp, loff_t pos , int mod)

{

        return 0;

}

static int test_open (struct inode *inode, struct file *filp) 

{

        static int count = 0;

        dev_t dev = inode->i_rdev;

        filp->f_pos = 16;

        test_data * ptr=kmalloc(sizeof(test_data), GFP_KERNEL);

        sprintf(ptr->info ,"NO %d called major=0x%x minor=0x%x",count,MAJOR(dev),MINOR(dev));

        ptr->offset = 0;

        count++;

        filp->private_data = ptr;//注意这里的filp每一个打开的设备(同一个设备代开多次)都会有一个独立的private_data

        return 0;

}

static int test_release (struct inode *inode, struct file *filp)

{

        kfree( filp->private_data ); 

        return 0;

}

static long test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

        test_data * ptr = filp->private_data;

        char * p = (char*)arg;

        char buf[100];

        if(copy_from_user(buf ,p ,100) == 0 )

        {

        }

        else

        {

                printk(KERN_ERR"copy arg error\r\n");

                return -1;

        }

        switch(cmd)

        {

                case SET1:

                        sprintf(ptr->info ,"%s cmd=%d",buf,cmd);

                        break;

                case SET2:

                        sprintf(ptr->info ,"%s cmd=%d",buf,cmd);

                        break;

                case SET3:

                        sprintf(ptr->info ,"%s cmd=%d",buf,cmd);

                        break;

        }

        ptr->offset = 0;

        return 0;

}

struct file_operations test_fops =

{

        .owner  = THIS_MODULE,

        .open = test_open,

        .release  = test_release,

        .read       = test_read, 

        .write      = test_write,

        .llseek     = test_seek,

        .unlocked_ioctl = test_ioctl, 

};

test_DEV_struct *test_dev; 

static int __init test_init(void)

{

        int err = -ENOMEM;

        int ret;

        int i;

        int major;

        

        test_dev = kmalloc(sizeof( test_DEV_struct), GFP_KERNEL);

        if(test_dev == NULL)

                return -ENOMEM;

          

        // alloc_chrdev_region\class_create\device_create这三个函数中指定的名字,能够是不一样的。也不影响驱动的运行

        ret = alloc_chrdev_region(&(test_dev->test_dev), 0, MAX_DEV, TEST_DEV_NAME);

        if( ret<0 )

        {

                goto fail1 ;

        }

        major = MAJOR(test_dev->test_dev); 

        memset(&(test_dev->test_cdev), 0, sizeof(struct   cdev));

         

        cdev_init(&(test_dev->test_cdev), &test_fops);

        

        ret = cdev_add(&(test_dev->test_cdev), test_dev->test_dev, MAX_DEV );

        if(ret<0)

        {

                goto fail2;

        } 

        test_dev->test_class = class_create(THIS_MODULE, TEST_DEV_NAME);

        

        for(i=0;i<MAX_DEV;i++)

        {

            device_create(test_dev->test_class, NULL, MKDEV(major ,i), NULL, "%s%d",TEST_DEV_NAME,i);

        }

        printk("test driver is successfully loaded\n");

        return 0;

fail2:

        unregister_chrdev_region(test_dev->test_dev, MAX_DEV);

 

fail1:  

        kfree(test_dev);

        return err; 

}

static void __exit test_exit(void)

{

        int i;

        //这里要注意注销的顺序。假设先注销了class在调用device_destroy,会造成异常

        for(i=0;i<MAX_DEV;i++)

        {

                device_destroy(test_dev->test_class, test_dev->test_dev+i);

        } 

        cdev_del(&(test_dev->test_cdev));

        unregister_chrdev_region(test_dev->test_dev, MAX_DEV);

        class_destroy( test_dev->test_class ); 

        

        kfree(test_dev);  

}

module_init(test_init);

module_exit(test_exit);





MODULE_AUTHOR("xxx Inc.");

MODULE_DESCRIPTION("testfor application");

//MODULE_LICENSE是定义模块许可的,这个一定不能漏掉。否则在载入模块的时候可能会出错。造成模块无法载入。

MODULE_LICENSE("GPL");









执行上面个的代码,我们就能够在/dev文件夹下看到mytest0 、mytest1 、mytest2三个设备节点了。在应用中就能够通过open close read write 对这些设备进行操作了。

可能大家另一个疑问,对于这个驱动,我们相应了三个设备,那么在open的时候我们怎么知道打开的究竟是mytest0 还是mytest1 或者mytest2呢?

这个问题我们能够通过inode->i_rdev来区分了,inode->i_rdev就是设备的设备号,我们创建的三个设备的次设备号各自是0、1、2。所以我们仅仅要读一下次设备号就知道相应的设备了。

linux 字符驱动的更多相关文章

  1. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl 0. 导语 在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后 ...

  2. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read.write 0. 导语 在上一篇博客里面,基于OMAPL138的字符驱动_GPIO驱动AD9833(一)之 ...

  3. linux 字符驱动框架(用户态的read,write,poll是怎么操作驱动的)

    前言 这篇文章是通过对一个简单字符设备驱动的操作来解释,用户态的读写操作是怎么映射到具体设备的. 因为针对不同版本的linux内核,驱动的接口函数一直有变化,这贴出我测试的系统信息: root@ubu ...

  4. 05 Linux字符驱动---静态注册

    1. mycdev.c #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h& ...

  5. [S5PV210 Linux字符驱动之PWM蜂鸣器驱动

    在SMDK210.C中添加如下beeper_device 结构体 static struct platform_device beeper_device = { .name = "pwm_b ...

  6. linux字符驱动之poll机制按键驱动

    在上一节中,我们讲解了如何自动创建设备节点,实现一个中断方式的按键驱动.虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的:在实际的应用场所里,有没有那么一种情 ...

  7. 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ

    基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ 0. 导语 学习进入到了下一个阶段,还是以AD9833为例,这次学习是向设备申请中断,实现触发,在未来很多场景,比如做用 ...

  8. linux驱动初探之字符驱动

    关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...

  9. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

随机推荐

  1. Powershell 远程管理

    一直使用 mstsc,为了防止墨菲定律,准备一些备用方案 环境,win10 to win12 winrm是windows 一种方便远程管理的服务: 首先要开启winrm service,便于在日常工作 ...

  2. XP中如何配置和共享打印机

    Win XP中如何配置和共享打印机                一.配置  打印机 在"控制面板"打开"打印机和传真",在左边的选项或单击右键选择" ...

  3. MSSQL注入SA权限不显错模式下的 入 侵

    一般新手扫到不显错的SA(systemadmin)的注入点时,虽然工具能猜表列目录但还是很麻烦有的人就直接放弃了,今天我给大家演示下如何利用.方法很简单大家看操作. 我这里使用的是 火狐的插件提交参数 ...

  4. linux服务器性能检测工具nmon使用

    今天介绍一款linux系统服务器性能检测的工具-nmon及nmon_analyser (生成性能报告的免费工具),亲测可用. 一.介绍 nmon 工具可以帮助在一个屏幕上显示所有重要的性能优化信息,并 ...

  5. java 实现统计某段文字在内容中出现的次数

    http://outofmemory.cn/code-snippet/815/java-zishutongji 一个api,位于apache.commons.lang.StringUtils类下的一个 ...

  6. java.math.BigDecimal保留两位小数,保留小数,精确位数

    http://blog.csdn.net/yuhua3272004/article/details/3075436 使用java.math.BigDecimal工具类实现   java保留两位小数问题 ...

  7. java之Cookie具体解释

    Cookie是由server端生成.发送给User-Agent(通常是浏览器).浏览器会将Cookie的key/value保存到某个文件夹下的文本文件内.下次请求同一站点时就发送该Cookie给ser ...

  8. php里面用魔术方法和匿名函数闭包函数动态的给类里面添加方法

    1.认识  __set  (在给不可访问属性赋值时,__set() 会被调用) 也就是说你再访问一个类里面没有的属性,会出发这个方法 class A{ private $aa = '11'; publ ...

  9. explicit 构造函数

    一.构造函数.默认构造函数.合成的默认构造函数 构造函数,是函数名与类名同样.没有返回类型的特殊的成员函数.能够有初始化列表. 默认构造函数,没有形參.或全部形參都有默认实參的构造函数. 假设没有显示 ...

  10. MVC进阶学习--View和Controller之间的数据传递(二)

    1. 使用Request.Form MVC 将页面简单化,与WebForm中的事件机制完全不同,就和普通的html标签表单提交没有任何区别(当然WebForm中的事件机制其实也是表单提交).在表单提交 ...