20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)
1. 設備驅動的作用
設備驅動充當了硬件和應用軟件的紐帶(那設備驅動和操作系統之間的關系是什么?)應用軟件時只需要調用系統軟件的應用編程接口(API)就可讓硬件去完成要求的工作。在系統沒有操作系統的情況下,工程師可以根據硬件設備的特點自行定義接口,如對串口定義SerialSend()、SerialRecv(),對LED定義LightOn()、LightOff(),對Flash定義FlashWr()、FlashRd()等。而在有操作系統的情況下,驅動的架構則由相應的操作系統定義,驅動工程師必須按照相應的架構設計驅動,這樣,驅動才能良好地整合入操作系統的內核中。
2. 無操作系統的設備驅動
有些環境下不需要操作系統就能完成,但一定會有設備驅動。
(而我目前的理解是:驅動就是一些設備功能的通用實現,設備是區別化的地址和變量信息)
一個例子:一個無限循環中夾雜著對設備中斷的檢測或者對設備的輪詢是這種系統中軟件的典型架構
5 if (serialInt == 1) 6 /* 有串口中斷 */ 7 { 8 ProcessSerialInt(); /* 處理串口中斷 */ 9 serialInt = 0; /* 中斷標志變量清 0 */ 10 } 11 if (keyInt == 1) 12 /* 有按鍵中斷 */ 13 { 14 ProcessKeyInt(); /* 處理按鍵中斷 */ 15 keyInt = 0; /* 中斷標志變量清 0 */ 16 } 17 status = CheckXXX(); 18 switch (status)其他模塊想要使用這個設備的時候,只需要包含設備驅動的頭文件serial.h,然后調用其中的外部接口函數。由此可見,在沒有操作系統的情況下,設備驅動的接口被直接提交給應用軟件工程師,應用軟件沒有跨越任何層次就直接訪問設備驅動的接口。驅動包含的接口函數也與硬件的功能直接吻合,沒有任何附加功能。所示為無操作系統情況下硬件、設備驅動與應用軟件的關系。
3. 有操作系統的設備驅動
驅動需要融入操作系統內核:為了實現這種融入,必須在所有設備的驅動上設計面向操作系統內核的接口。在有操作系統的時候,驅動變成了連接內核與硬件的橋梁,它對外呈現為操作系統的API, 不再給應用軟件工程師提供直接接口。
以驅動的復雜換取上層應用的便利。
4. Linux設備驅動
驅動針對的是存儲器和外設(包括CPU內部集成的存儲器和外設,但不針對于CPU內核)
Linux將存儲器和外設分為3個基礎大類:
- 字符設備? ?字符設備必須以串行順序依次進行訪問的設備,例如觸摸屏,磁帶驅動器,鼠標等。
- 塊設備? ?可以按任意順序進行訪問,以塊為單位進行操作,如硬盤,EMMC
以上兩種設備均可以使用文件系統的操作接口open() close() read() write()等進行訪問
- 網絡設備? ? ?面向數據包的接收和發送設計
Linux設備驅動與整個軟硬件系統關系
5. QEMU實驗平臺
6. 閱讀Linux源碼
7. 設備驅動Hello world: LED驅動
無操作系統時的LED驅動
在嵌入式系統的設計中,LED一般直接由CPU的GPIO(通用可編程I/O)口控制。GPIO一般由兩組寄存器控制,即一組控制寄存器和一組數據寄存器。控制寄存器可設置GPIO口的工作方式為輸入或者輸出。當引腳被設置為輸出時,向數據寄存器的對應位寫入1和0會分別在引腳上產生高電平和低電平;當引腳設置為輸入時,讀取數據寄存器的對應位可獲得引腳上的電平為高或低。
在本例子中,我們屏蔽具體CPU的差異,假設在GPIO_REG_CTRL物理地址中控制寄存器處的第n位寫入1可設置GPIO口為輸出,在地址GPIO_REG_DATA物理地址中數據寄存器的第n位寫入1或0可在引腳上產生高或低電平,則在無操作系統的情況下,設備驅動見代碼清單
#define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))#define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))/* 初始化 LED */void LightInit(void) {reg_gpio_ctrl |= (1 << n); /* 設置 GPIO 為輸出 */ // |= 按位或后賦值} /* 點亮 LED */ void LightOn(void){reg_gpio_data |= (1 << n); /* 在 GPIO 上輸出高電平 */} /* 熄滅 LED */ void LightOff(void){reg_gpio_data &= ~ (1 << n); /* 在 GPIO 上輸出低電平 */}linux下的LED驅動
在內核中實際實現了一個提供sysfs節點的GPIO LED驅動。
但這里,可以使用字符設備驅動的框架編寫對應的LED設備驅動,操作硬件的LightInit()、LightOn()、LightOff()函數仍然需要,但是,遵循Linux編程的命名習慣,重新將其命名為light_init()、light_on()、light_off()。這些函數將被LED設備驅動中獨立于設備并針對內核的接口進行調用,給出了Linux下的LED驅動。
1 #include .../* 包含內核中的多個頭文件 */ 2 /* 設備結構體 */ 3 struct light_dev { 4 struct cdev cdev; /* 字符設備 cdev 結構體 */ 5 unsigned char vaule; /* LED 亮時為 1 ,熄滅時為 0 ,用戶可讀寫此值 */ 6 }; 7 struct light_dev *light_devp; 8 int light_major = LIGHT_MAJOR; 9 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); 10 MODULE_LICENSE("Dual BSD/GPL"); 11 /* 打開和關閉函數 */ 12 int light_open(struct inode *inode, struct file *filp) 13 { 14 struct light_dev *dev; 15 /* 獲得設備結構體指針 */ 16 dev = container_of(inode->i_cdev, struct light_dev, cdev); //container_of 17 /* 讓設備結構體作為設備的私有信息 */ 18 filp->private_data = dev; 19 return 0; 20 } 21 int light_release(struct inode *inode, struct file *filp) 22 { 23 return 0; 24 } 25 /* 讀寫設備 : 可以不需要 */ 26 ssize_t light_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) 28 { 29 struct light_dev *dev = filp->private_data; /* 獲得設備結構體 */ 30 if (copy_to_user(buf, &(dev->value), 1)) 31 return -EFAULT; 32 return 1; 33 } 34 ssize_t light_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos) 36 { 37 struct light_dev *dev = filp->private_data; 38 if (copy_from_user(&(dev->value), buf, 1)) 39 return -EFAULT; 40 /* 根據寫入的值點亮和熄滅 LED */ 41 if (dev->value == 1) 42 light_on(); 43 else 44 light_off(); 45 return 1; 46 } 47 /* ioctl 函數 */ 48 int light_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg) 50 { 51 struct light_dev *dev = filp->private_data; 52 switch (cmd) { 53 case LIGHT_ON: 54 dev->value = 1; 55 light_on(); 56 break; 57 case LIGHT_OFF: 58 dev->value = 0; 59 light_off(); 60 break; 61 default: 62 /* 不能支持的命令 */ 63 return -ENOTTY; 64 } 65 return 0; 66 } 67 struct file_operations light_fops = { 68 .owner = THIS_MODULE, 69 .read = light_read, 70 .write = light_write, 71 .ioctl = light_ioctl, 72 .open = light_open, 73 .release = light_release, 74 }; 75 /* 設置字符設備 cdev 結構體 */ 76 static void light_setup_cdev(struct light_dev *dev, int index) 77 { 78 int err, devno = MKDEV(light_major, index); 79 cdev_init(&dev->cdev, &light_fops); 80 dev->cdev.owner = THIS_MODULE; 81 dev->cdev.ops = &light_fops; 82 err = cdev_add(&dev->cdev, devno, 1); 83 if (err) 84 printk(KERN_NOTICE "Error %d adding LED%d", err, index); 85 } 86 /* 模塊加載函數 */ 87 int light_init(void) 88 { 89 int result; 90 dev_t dev = MKDEV(light_major, 0); 91 /* 申請字符設備號 */ 92 if (light_major) 93 result = register_chrdev_region(dev, 1, "LED"); 94 else { 95 result = alloc_chrdev_region(&dev, 0, 1, "LED"); 96 light_major = MAJOR(dev); 97 } 98 if (result < 0) 99 return result; 100 /* 分配設備結構體的內存 */ 101 light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL); 102 if (!light_devp) { 103 result = -ENOMEM; 104 goto fail_malloc; 105 } 106 memset(light_devp, 0, sizeof(struct light_dev)); 107 light_setup_cdev(light_devp, 0); 108 light_gpio_init(); 109 return 0; 110 fail_malloc: 111 unregister_chrdev_region(dev, light_devp); 112 return result; 113 } 114 /* 模塊卸載函數 */ 115 void light_cleanup(void) 116 { 117 cdev_del(&light_devp->cdev); /* 刪除字符設備結構體 */ 118 kfree(light_devp); /* 釋放在 light_init 中分配的內存 */ 119 unregister_chrdev_region(MKDEV(light_major, 0), 1); /* 刪除字符設備 */ 120 } 121 module_init(light_init); 122 module_exit(light_cleanup);以上兩段代碼量的對比中,除了基本的燈的開關功能外,還包括了結構體如結構體file_operations、cdev,Linux內核模塊聲明用的MODULE_AUTHOR、MODULE_LICENSE、module_init、module_exit,以及用于字符設備注冊、分配和注銷的函register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()等。
此時,我們只需要有一個感性認識,那就是,上述暫時陌生的元素都是Linux內核為字符設備定義的,以實現驅動與內核接口而定義的。Linux對各類設備的驅動都定義了類似的數據結構和函數。
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux搭建测试环境详细步骤
- 下一篇: 【linux内核分析与应用-陈莉君】字符