天天看點

[Linux驅動]字元裝置驅動學習筆記(一)

一,主裝置号和次裝置号代表的含義?linu核心是如果根據主裝置号找驅動,次裝置号找裝置的。

答:通常一個主裝置号代表一個驅動,比如在block裝置中,一個主裝置号代表一個emmc裝置,不同次裝置号代表的是不同的分區

Linux核心允許多個驅動共享一個主裝置号,但更多的裝置都遵循一個驅動對一個主裝置号的原則。核心維護者一個以主裝置号為key的全局哈希表,而哈希表中資料部分則為與該主裝置号裝置對應的驅動程式(隻有一個次裝置)的指針或者多個次裝置驅動程式組成的數組的指針(次裝置共享主裝置号)

二,編寫字元裝置的一般順序

一,調用kmalloc memset函數對相關結構體(比如裝置結構體)進行初始化的動作

二,注冊相應的驅動,如平台驅動則調用platform_driver_register(&driver)進行注冊,driver是一個全部靜态變量。

struct test_dev{
	struct cdev cdev;
	char *test_devname;
	char actname[32];
	unsigned int index;
	struct semphare sem;
}
static struct platform_driver test_driver = {
	.probe = test_probe,
	.remove = test_remove,
	.driver = {
		.name = "test_char",
		.owner = THIS_MODULE,
	},
};
	
		
#define TEST_NUM 10
static struct test_dev *test_devices;
static int __init test_init(void)
{
	int result;
	test_devices=kmalloc(TEST_NUM*sizeof(struct test_dev),GFP_KERNEL);
	if(!test_devices)
	{
		result = -ENOMEM
		printk(“alloc test_devices fail\n”);
		goto fail_malloc;
	}
	memset(test_devices,0,TEST_NUM*sizeof(struct test_dev));
	result = platform_driver_register(&test_driver);
	if (result)
	{
		printk("fail to register test_driver");
		goto fail_driver_register;
	}
	return 0
fail_driver_register:
	return result;
fail_malloc:
	return result;
}
module_init(test_init);
           

三,接來下會調用到test_probe()函數,該函數首先alloc_chrdev_region()函數配置設定主裝置号和次裝置号,然後調用cdev_init()函數來注冊真正的字元裝置,void cdev_init(struct cdev*cdev,struct file_operations *fops)最後調用cdev_add()函數來告訴核心該結構體的資訊init cdev_add(struct cdev*cdev,dev_t num,unsigned int count)

int test_probe(struct platform_device *dev)
{
	int result;
	dev_t devno;
	result = alloc_chrdev_region(&devno,0,TEST_NUM,"testchar");
	if(result<0)
	{
		printk();
		goto fail_alloc_chrdev;
	}
	major = MAJOR(devno);
	for(int i=0;i<TEST_NUM;I++)
	{
		devno=MKDEV(major,i);
		cdev_init(&test_devices[i].cdev,&test_fops);
		test_devices[i].cdev.owner = THIS_MODULE;
		test_devices[i].cdev.ops = &test_fops;
		result = cdev_add(&test_devices[i].cedv,devno,1);
		if(result)
		{
			printk("cdev add fail\n");
			goto fail_register_chrdev;
		}

	}
	return 0;
fail_register_chrdev:
	cdev_del(&test_devices[i].cdev);
	unregister_chrdev_region(MKDEV(major,0),TEST_NUM);

}
           

三,阻塞型I/O

如果在調用字元裝置的read write方法中,裝置沒有準備好可能導緻使用者層要去讀取的程序阻塞(預設),将其置入休眠直到請求可以繼續。将程序置入休眠要注意的兩點

(1)不要再原子上下文中進行休眠,驅動程式不能再擁有自旋鎖,RCU鎖時候休眠,擁有信号量的程序休眠是合法的,但是等待此信号量的其它程序也必須休眠,是以擁有信号量的程序休眠時間要足夠短

(2)當程序喚醒的時候不知道發生過什麼,是以檢查以確定我們等待的條件真正為真。

讓程序休眠的方法:

wait_event()相關的函數

喚醒程序方法

wake_up()相關的函數

如何實作非阻塞的I/O操作

答:填充filp->f_flags中的O_NONBLOCK flag,如果以非阻塞方式打開,如果此時裝置沒有就緒好的資料,那麼會傳回-EAGAIN錯誤。

四,裝置檔案的通路控制

1,讓一個程序獨享裝置,通過維護一個原子變量

使用執行個體:adb的驅動程式,每次隻讓adbd一個程序使用該裝置。

static int scull_s_poen(struct inode*inode,struct file*filp)
{
	struct scull_dev *dev = &scull_s_device;
	if(!atomic_dec_and_test(&scull_s_available)){
		atomic_inc(&scull_s_available);
		return -EBUSY;
	}
	if((filp->flags & O_ACCMODE)==O_WRONLY)
		SCULL_trim(dev);
	filp->priate_data=dev;
	return 0;
}
           

2,限制每次隻由一個使用者通路

spin_lock(&scull_u_lock);		//scull_u_lock是全局變量,是以用自旋鎖,自選鎖使用過程不能睡眠
if(scull_u_count && (scull_u_owner != current->uid) &&(scull_u_owner != current->euid) && !capable(CAP_DAC_OVERRIDE) )
{
	spin_unlock(&scull_u_lock);
	return -EBUSY;
}
if(scull_u_count == 0)
{
	scull_u_owner=current->uid;  //第一個通路裝置的使用者為屬主使用者
}
scull_u_count++;
spin_unlock(&scull_u_lock);
           

繼續閱讀