原创 linux 字符設備驅動

2010-7-2 10:55 3850 1 1 分类: MCU/ 嵌入式

字符設備驅動結構


cdev結構體


 


2.6內核使用cdev結構描述字符設備


struct cdev


{


      struct kobject kobj;


      struct  module *owner; /*所屬模塊*/


      struct file_operations *ops; /*文件操作結构體*/


      struct  list_head list;


      devt_t dev; /*設備號*/


      unsigned int count;


};


dev定義設備號,32位,高12位為主設備號,低20位為次設備號。


MAJOR(dev_t dev) 獲得主設備號


MINOR(dev_t dev)獲得次設備號


MAKDEV(int major, int minor)生成設備號


 


file_operations定義了字符設備驅動提供給虛擬文件系統的接口函數.


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


struct cdev *cdev_alloc(void);


int cdev_add(struct cdev *, dev_t, unsigned);


void cdev_del(struct cdev *);


cdev_init()用於初始化cdev的成員,並建立cdev和file_operations的連接,源代碼如下:


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


{


    memset(cdev, 0, sizeof *cdev);


    INIT_LIST_HEAD(&cdev->list);


    cdev->kobj.ktype=&ktype_cdev_default;


    kobject_init(&cdev->kobj);


    cdev->ops=fops; /*fops指針賦給cdev->ops*/


}


cdev_alloc()用於動態申請一個cdev內存,源代碼如下:


struct cdev *cdev_alloc(void)


{


    struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL);


    if (p) {


          memset(p, 0, sizeof(struct cdev));


          p->kobj.ktype=&ktype_cdev_dynamic;


          INIT_LIST_HEAD(&p->list);


          kobject_init(&p->kobj); 


    }


   return p;


}


cdev_add()和cdev_del()分別向系統添加和刪除一個cdev,完成字符設備的注冊和注銷。


 


分配和釋放設備號


在調用cdev_add()向系統注冊設備前,應首先調用register_cdrdev_region()或alloc_chrdev_region()向系統申請設備號。


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


int alloce_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,


    const char  *name);


register_chrdev_region()用於已知起始設備的設備號的情況,而alloc_cdrdev_region()用於設備號未知,向系統動態申請未被占用設備號的情況。申請成功,會把設備號放入第一個參數dev中。alloc_register_region()會自動避開設備號的重復的沖突。


相反地,在調用cdev_del()從系統注銷字符設備之后,unregister_chrdev_region()應被調用用於釋放原先申請的設備號。


 void unregister_chredv_region(dev_t from, unsigned count);


 


file_operations結构體


       file_operations結构體中的成員函數是字符設備驅動程序的主體,這些函數會在應用程序調用open(),close(),read(),write()等系統調用時被調用。


struct file_operations


{


    struct module *owner;


    loff_t(*llseek)(struct file *, loff_t, int);//用來修改文件當前位置


   ssizze_t(*read)(struct file *, char __user *, size_t, ;off_t*);//從設備中同步讀取數據


    ssize_t(*aio_read)(struct kiocb *, char __user *, size_t, loff_t);//初始化一個异步讀取操作


    ssize_t(*write)(struct file*, const char __user *, size_t, loff_t);//向設備寫數據


    ssize_t(*aio_write)(struct kiocb *, const char __user *, size_t, loff_t);//异步寫操作


    int(*readdir)(struct file *,void *, filldir_t);//僅用於讀取目錄,對於設備,該字段為NULL


    unsigned int(*poll)(struct file *, struct poll_table_struct*);//輪詢,判斷是否可以進行非阻塞的讀寫


    int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);//設備I/O控制


    long(*unlocked_ioctl)(struct inode *, struct file *, unsigned int, unsigned long);//不使用BLK系統,設備I/O控制


     long(*compat-ioctl)(struct inode *, struct file *, unsigned int, unsigned long);//64位系統,32位ioctl調用


    int(*mmap)(struct file *, struct vm_area_struct*);//將設備內存映射到進程地址空間


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


    int(*flush)(struct file*);


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


    int(*synch)(struct file *, struct dentry *, int datasync);//刷新待處理的數據


    int(*aio_fsync)(struct kiocb *, int datasync);//异步fsync


    int(*fasync)(int, struct file *, int);//通知設備FASYNC標志發生變化


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


    ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t *);


    ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t *);//分散/聚集型的讀寫操作


     ssize_t(*sendfile)(struct file *, loff_t *, size_t,read_actor_t,void*);//通常為NULL   


     ssize_t(*sendpage)(struct file *, struct page *, int, size_t,loff_t *, int);//通常為NULL       


      unsigned long(*get_unmapped_area)(struct file *, unsigned long, unsigned long,              unsigned long, unsigned long);//在進程的地址空間找到一個將低層設備中的內存段映射的位置


     int(*check_flags)(int);//允許模塊檢查傳遞給fcntl(F_SETEL...)調用的標志


     int(*dir_notify)(struct file *filp,unsigned long arg);//僅對文件系統有效,驅動程序不必實現


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


};


llseek()修改一個文件的當前的讀寫位置,並返回新位置,出錯時返回一個負值。


read()從設備中讀取數據,成功返回讀取的字節數,出錯返回負值。


write()向設備發送數據,成功返回寫入的字節數。如果此函數未被實現,當用戶調用write()系統調用時,將得到-EINVAL返回值。


readdir()僅用於目錄,設備節點不需要實現它。


iotcl()提供設備相關控制命令的實現,當調用成功時,返回給調用程序一個非負值。內核本身識別部分控制命令,而不必調用設備驅動中的iotcl()。如果設備不提供iotcl(),對於內核不能識別的命令,用戶進行iotcl()系統調用時將獲得-EINVAL返回值。


mmap()將設備內存映射到進程內存中,如果設備驅動未實現此函數,用戶進行mmap()系統調用時將獲得-ENODEV返回值。這個函數對於幀緩沖等設備特別有意義。


當用戶空間調用LinuxAPI函數open()打開設備文件時,設備驅動的open()最終被調用。驅動程序可以不實現這個函數,在這種情況下,設備打開操作永遠成功。


poll()一般用于詢問設備是否可以被非阻塞地立即讀寫。當詢問條件未角發時,用戶空間進行select()和poll()系統調用將引起進程的阻塞。


aio_read()和aio_write()分別對與文件描述符對應的設備進行异步讀,寫操作。設備實現這兩個函數後,用戶可以對設備文件描述符調用aio_read(),aio_write()系統調用。


 


字符設備驅動的組成


Linux系統中,字符設備驅由如下幾個部分組成。


1.字符設備驅動模塊加載與卸載函數


加載函數中應實現設備號的申請和cdev的注冊,而卸載函數中應實現設備號的釋放和cdev的注銷。


通常習慣將設備定義為一個設備相關的結构體,其包含設備涉及的cdev,私有數據及信號量等信息。常見的設備結构體,模塊加載和卸載函數形式代碼如下。


//設備結构體


struct xxx_dev_t


{


    struct cdev cdev;


    ...


} xxx_dev;


//設備驅動模塊加載函數


static int __init xxx_init(void)


{


    ...


    cdev_init(&xxx_dev.cdev, &xxx_fops);//初始化cdev


    xxx_dev.cdev.owner=THIS_MODULE;


    //獲取字符設備號


    if (xxx_major)


    {


          register_chrdev_region(xxx_dev_no,1,DEV_NAME);


    }


    else


     {


          alloc_chrdev_region(xxx_dev_no,0,1,DEV_NAME);


     }


   


     ret = cdev_add(&xxx_dev.cdev,xxx_dev_no,1);//注冊設備


     ...


}


//設備驅動模塊卸載函數


static void __exit xxx_exit(void)


{


     unregister_chrdev_region(xxx_dev_no,1);//釋放占用的設備號


     cdev_del(&xxx_dev.cdev);//注銷設備


     ...


   }    


}


2.字符設備驅動的file_operations結构體中成員函數


file_operations結构體中成員函數是字符設備驅動與內核的接口,是用戶空間對linux進行系統調用的最終落實者。大多數字符設備驅動會實現read(),write(),iotcl()函數,常見的字符設備驅動這3個函數的形式代碼如下。


//讀設備


sszie_t xxx_read(struct file* filp,char __user *buf,size_t count)


{


     ...


    copt_to_user(buf,...,..);


    ...


}


//寫設備


ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count, loff_t *f_ops)


{


     ...


    copy_from_user(..,buf,...);


    ...


}


//ioctl


int xxx_ioctl(struct inode *inode, struct file *filp,unsigned int cmd,unsigned long arg)


{


     ...


    switch (cmd)


   {


        case xxx_CMD1:


              ...


              break;


         case xxx_CMD2:


              ...


              break;


          default://不能支持的命令


               return -ENOTTTY;


   }


    return 0;


}


設備驅動的讀寫函數中,filp是文件結构體的指針,buf是用戶空間的內存地地址,該地址在內核空間不能直接讀寫,count是要讀寫的字節數,f_ops是讀的位置相對於文件開頭的偏移。


由於內核空間與用戶空間的內存不能直接互訪,因此借函數copy_from_user()完成用戶空間到內核空間的復制,copy_to_user()完成內核空間到用戶空間的復制。


unsigned long copy_from_user(void *to,const void __user *from,unsiged long long count);


unsigned long copy_to_user(void __user *to, const void *from,unsigned long long count);


上述函數均返回不能被復制的字節數,因此,如果完全復制面功返回0。


如果復制的內存是簡單類型,如char,int,long,等,則可以使用簡單的put_user()和get_user(),如下.


int val;//內核空間整型變量


...


get_user(val,(int*)arg);//用戶空間到內核空間,arg是用戶空間地址


...


put_user(val,(int*)arg);//內核空間到用戶空間


__user宏定義如下。


#ifdef __CHECKER__


#define __USER  __attribute__((noderef,address_space(1)))


#else


#define __user


#endif


ioctl()中arg為cmd對應的參數。


在字符驅動中,需要定義一個file_operations的實例,並將具體設備驅動的函數賦值給file_operations的成員。


struct file_operations xxx_fops =


{


    .owner = THIS_MODULE,


    .read = xxx_read,


    .write = xxx_write,


    .ioctl = xxx_ioctl,


    ...


};


 


 


 


 


 

文章评论0条评论)

登录后参与讨论
我要评论
0
1
关闭 站长推荐上一条 /2 下一条