嵌入式核心及驅動開發之學習筆記(四) 規範化程式碼
前面筆者已實現了使用者程式控制led燈閃爍的驅動程式碼,但是由於程式碼不規範,顯得亂七八糟的,因此需要規範化。如果比較大一點的工程沒有規範的話,也不利於後期的跟新與維護。分析先前的程式不規範點有二:
- 定義的變數多而且散亂
- 程式沒有錯誤處理機制
C語言雖然是面向過程的語言,但是可以利用結構體來實現面向物件的思想。通過引入面向物件的思想,來解決第一個問題。通過結構體將將相關的變數型別進行一次封裝,構造出一個物件;而對於錯誤處理, 可以通過prink提示錯誤資訊,然後goto語句跳轉到錯誤處理的過程來處理。
面向物件
在面向物件的思想中,一切皆是物件。將led裝置抽象成為一個物件,那它的主裝置號、暫存器基地址等資訊都可以看做它的屬性,我們用結構體將這樣一些資料型別進行封裝。
struct led_desc{
//宣告結構體型別,描述led資訊
unsigned int dev_major; //描述主裝置號
struct class *cls;
struct device *dev;
void *reg_virt_base; //暫存器基地址
};
定義結構體物件 ,led_dev明顯是一個結構體指標
struct led_desc *led_dev;
為led_dev分配空間,物件的例項化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
如果要引用這個物件的屬性(結構體訪問成員變數),例如
led_dev->dev_major
因為使用malloc分配的是塊空間(堆),結束時需要手動釋放資源(空間)
kfree(led_dev);
錯誤處理機制
當程式申請資源失敗,我們不僅要分析判斷這個錯誤,還要列印錯誤提示
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0) {//列印錯誤 printk(KERN_ERR "register_chrdev error\n"); }
但是,,這樣的做法是不對的。我們程式不能直接丟擲錯誤資訊,然後退出,給系統留下一個爛攤子啊!因為之前可能存在,已申請但是沒有被釋放的資源。正確的做法
錯誤源 ---> 程式判斷 ---> (列印錯誤資訊) ---> 設定錯誤碼 ---> 跳轉到錯誤處理 ---> 退出
int ret;
//led_dev分配空間,物件的例項化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
//動態向系統申請裝置號
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
err_0:
kfree(led_dev);
return ret;
總之,錯誤處理是要處理那些 在錯誤出現之前申請的資源,將其回收。
驅動程式碼
下面是對之前led驅動程式的一些改進。
- 使用了結構體來描述裝置資訊
- 使用goto進行錯誤處理
- 修改申請主裝置號為動態方式
- 使用readl writel 介面函式讀寫地址
//led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos);
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos);
int led_drv_open(struct inode *inode, struct file *filp);
int led_drv_close(struct inode *inode, struct file *filp);
#define GPX2_CON 0x11000C40
#define GPX2_SIZE 8
//volatile unsigned long *gpx2conf;
//volatile unsigned long *gpx2dat;
//static unsigned int dev_major = 250;
//static struct class *devcls;
//static struct device *dev;
const struct file_operations my_fops = {
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
struct led_desc{
//宣告結構體型別,描述led資訊
unsigned int dev_major; //描述主裝置號
struct class *cls;
struct device *dev;
void *reg_virt_base; //暫存器基地址
};
//定義一個結構體變數,建立物件
struct led_desc *led_dev;
static int __init led_drv_init(void)
{
int ret;
printk("-------%s-------------\n", __FUNCTION__);
//led_dev分配空間,物件的例項化
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
//動態向系統申請裝置號
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
//建立裝置結點
led_dev->cls = class_create(THIS_MODULE, "led_cls");
if(IS_ERR(led_dev->cls))
{
printk(KERN_ERR "class_create error\n");
ret = PTR_ERR(led_dev->cls); //½«Ö¸Õë³ö´íµÄ¾ßÌåÔÒòת»»³ÉÒ»¸ö³ö´íÂë
goto err_1;
}
led_dev->dev = device_create(led_dev->cls, NULL,
MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
if(IS_ERR(led_dev->dev))
{
printk(KERN_ERR "device_create error\n");
ret = PTR_ERR(led_dev->dev); //½«Ö¸Õë³ö´íµÄ¾ßÌåÔÒòת»»³ÉÒ»¸ö³ö´íÂë
goto err_2;
}
//將實體地址對映成為虛擬地址,用指標指向這個地址
led_dev->reg_virt_base = ioremap(GPX2_CON, GPX2_SIZE);
if(led_dev->reg_virt_base == NULL)
{
printk(KERN_ERR "ioremap error\n");
ret = -ENOMEM;
goto err_3;
}
//GPX2_7設定成輸出模式
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_base);
return 0;
err_3:
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
err_2:
class_destroy(led_dev->cls);
err_1:
unregister_chrdev(led_dev->dev_major, "led_dev_test");
err_0:
kfree(led_dev);
return ret;
}
static void __exit led_drv_exit(void)
{
printk("-------%s-------------\n", __FUNCTION__);
//取消地址對映
iounmap(led_dev->reg_virt_base);
//銷燬這個裝置結點
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
//釋放這個裝置號
unregister_chrdev(led_dev->dev_major, "led_dev_test");
//釋放結構體空間
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
static int kernel_val = 555;
// read(fd, buf, size);
ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------\n", __FUNCTION__);
int ret;
ret = copy_to_user(buf, &kernel_val, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
return 0;
}
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------\n", __FUNCTION__);
int ret;
int value;
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
if(value){
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
}else{
writel( readl(led_dev->reg_virt_base + 4) & ~(1<<7), led_dev->reg_virt_base + 4 );
}
return 0;
}
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("-------%s-------\n", __FUNCTION__);
return 0;
}
檢視實驗結果
修改應用程式,識別裝置結點為 /dev/led0
編譯並移動檔案到nfs根目錄
[email protected]:/mnt/hgfs/sharefolder/kernel/linux-3.14-fs4412/drivers/mydrivers/chr_drv# make
[email protected]:/mnt/hgfs/sharefolder/kernel/linux-3.14-fs4412/drivers/mydrivers/chr_drv# make install
開發板載入模組,執行應用程式
[[email protected] drv_module]# ls
chr_drv.ko chr_test led_drv.ko led_test
[[email protected] drv_module]# insmod led_drv.ko
[ 5097.315000] -------led_drv_init-------------
[[email protected] drv_module]# ./led_test
[ 5104.010000] -------led_drv_open-------
又可以觀察開發板上led是閃爍狀態了。