1. 程式人生 > >嵌入式核心及驅動開發之學習筆記(四) 規範化程式碼

嵌入式核心及驅動開發之學習筆記(四) 規範化程式碼

前面筆者已實現了使用者程式控制led燈閃爍的驅動程式碼,但是由於程式碼不規範,顯得亂七八糟的,因此需要規範化。如果比較大一點的工程沒有規範的話,也不利於後期的跟新與維護。分析先前的程式不規範點有二:

  1. 定義的變數多而且散亂
  2. 程式沒有錯誤處理機制

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是閃爍狀態了。