第一次字符驅動編程的問題總結

創建字符設備節點的方法

  • mknod /dev/xxx   c  252 0


在編寫驅動程序時:

主要作用:1) 指定函數在內存的存放位置

     2)在kernel初始化後期,釋放所有這些函數代碼所佔的內存空間

經常在入口函數和出口函數里加上 __init  ,__exit,作用如下:

module_init除了初始化加載之外,還有後期釋放內存的作用。linux kernel中有很大一部分代碼是設備驅動代碼,這些驅動代碼都有初始化和反初始化函數,這些代碼一般都只執行一次,爲了有更有效的利用內存,這些代碼所佔用的內存可以釋放出來

linux就是這樣做的,對只需要初始化運行一次的函數都加上__init屬性,__init 宏告訴編譯器如果這個模塊被編譯到內核則把這個函數放到(.init.text)段,module_exit的參數卸載時同__init類似,如果驅動被編譯進內核,則__exit宏會忽略清理函數,因爲編譯進內核的模塊不需要做清理工作,顯然__init和__exit對動態加載的模塊是無效的,只支持完全編譯進內核。

在kernel初始化後期,釋放所有這些函數代碼所佔的內存空間。連接器把帶__init屬性的函數放在同一個section裏,在用完以後,把整個section釋放掉。當函數初始化完成後這個區域可以被清除掉以節約系統內存。Kenrel啓動時看到的消息“Freeing unused kernel memory: xxxk freed”同它有關。



一般來說,寄存器的地址是物理地址,而在系統中用的都是虛擬地址,而要實現寄存器的物理地址轉化爲虛擬地址:

都要在入口函數調用ioremap函數, 定義如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

在出口函數調用void iounmap(void * addr);

例:

在入口函數:
static int __init hello_init(void)
{
  voliate unsigned long *gpfcon

  gpfcon = (voliate ussigned long *)iroeamp(0xbfd010C0,16);
}

在出口函數:
static void __exit hello_exit(void)
{
   iounmap(gpfcon);
}

但在loongson開發板上:

而在以mips架構的loongson開發板上的寄存器無需調用ioremap,具體原因是在loongson 開發板實現了寄存器的物理地址等於虛擬地址,,無需轉化,

但即使虛擬地址與物理地址相同,都需要經過MMU調度實現虛擬地址轉化爲物理地址。


在實現用戶與內核之間數據的傳遞時,一般要調用以下兩個函數:

作用:從內核區中讀取數據到用戶區
簡述:
頭文件:#include <linux/uaccess.h>
  • static inline unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
如果數據拷貝成功,則返回零;否則,返回沒有拷貝成功的數據字節數。
*to是用戶空間的指針,
*from是內核空間指針,
n表示從內核空間向用戶空間拷貝數據的字節數

作用:從用戶區讀取數據到內核區
簡述:
  • static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) 
如果數據拷貝成功,則返回零;否則,返回沒有拷貝成功的數據字節數。
*to是用戶空間的指針,
*from是內核空間指針,
n表示從內核空間從用戶空間拷貝數據的字節數

 //上述兩個函數針對字針的傳遞,



// 實現用戶與內核值的調用,一般用ioctl函數
ioctl 函數接口語法要點
頭文件:#include <linux/fs.h>

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

inode:文件的內核內部結構指針
filp:被打開的文件描述符
cmd:命令類型
arg:命令相關參數



當在內核編譯模塊時,

WARNING: "register_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_add" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_init" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "alloc_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "printk" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "unregister_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_del" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
  CC      /home/cam/桌面/hello_drive/hello_drive.mod.o
/home/cam/桌面/hello_drive/hello_drive.mod.c:8: error: variable '__this_module' has initializer but incomplete type
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: error: unknown field 'name' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: warning: (near initialization for '__this_module')
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: error: unknown field 'init' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: warning: (near initialization for '__this_module')
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: error: unknown field 'arch' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: error: 'MODULE_ARCH_INIT' undeclared here (not in a function)
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: warning: (near initialization for '__this_module')
make[2]: *** [/home/cam/桌面/hello_drive/hello_drive.mod.o] 錯誤 1

當出現以下情況時:


error:variable '__this_module' has initializer but incomplete type

error: unknown field 'init' specified in initializer


一般是你在內核中未加入模塊支持選項

解決辦法:make menuconfig 選中enable loadable module suppot選項即可 ,然後重新make



字符設備的生成過程:

1)設備號分配與釋放

int register_chrdev_region (dev_t first, unsigned int count, char *name)  // 靜態已有的設備號

int alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name)  //動態申請設備號,

void unregister_chrdev_region (dev_t first, unsigned int count)   //註銷設備號


2)在獲得了系統分配的設備號之後,通過註冊設備才能實現設備號和驅動程序之間的關聯

各個函數如下:

頭文件:#include <linux/cdev.h>        

sturct cdev *cdev_alloc(void)            

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

int cdev_add (struct cdev *cdev, dev_t num, unsigned int count)          

void cdev_del(struct cdev *dev)

在 Linux 內核中使用 struct cdev 結構來描述字符設備,我們在驅動程序中必須將已分配到的設備號以及設備操作接口(即爲 struct file_operations 結構)賦予struct cdev 結構變量。1)首先使用 cdev_alloc()函數向系統申請分配 struct cdev 結構, ( 附註:也可以直接定義 struct cdev 結構 ,而不用調用cdev_alloc)

2)再用 cdev_init()函數初始化已分配到的結構並與 file_operations 結構關聯起來。

3)最後調用 cdev_add()函數將設備號與 struct cdev 結構進行關聯並向內核正式報告新設備的註冊,這樣新設備可以被用起來了。

流程如下:

sturct cdev *cdev_alloc(void)函數->生成struct cdev-> void cdev_init(struct cdev *cdev, struct file_operations *fops)

->int cdev_add (struct cdev *cdev, dev_t num, unsigned int count)


4)如果要從系統中刪除一個設備,則要調用 cdev_del()函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章