字符設備驅動結構
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条评论)
登录后参与讨论