天天看點

S3C2440 字元裝置的另一種寫法register_chrdev_region()來注冊(二十九)

https://www.cnblogs.com/lifexy/p/7827559.html

1、之前注冊字元裝置用的如下函數注冊字元裝置驅動:

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
           

但其實這個函數是linux版本2.4之前的注冊方式,它的原理是:

(1)确定一個主裝置号

(2)構造一個file_operations結構體,然後放在chrdevs數組中

(3)注冊:register_chrdev

然後當讀寫字元裝置的時候,就會根據主裝置号從chrdevs數組中取出相應的結構體,并調用相應的處理函數。

它會有個很大的缺點:

每注冊一個字元裝置的時候,還會連續注冊0~255個次裝置号,使它們綁定在同一個file_operations操作方法結構體上,在大多數情況下,都隻用極少的次裝置号,是以會浪費很多資源。

2、是以在2.4版本後,核心裡就加入了以下幾個函數也可以來實作注冊字元裝置:

分為:靜态注冊(指定裝置編号來注冊)、動态配置設定(不指定裝置編号來注冊),以及有連續注冊的次裝置編号範圍區間,避免了register_chrdev()浪費資源的缺點

2.1:指定裝置編号來靜态注冊一個字元裝置

int register_chrdev_region(dev_t from, unsigned count, const char *name)
           

from:注冊的指定起始裝置編号,比如:MKDEV(100, 0),表示起始主裝置号100,起始次裝置号為0

count:需要連續注冊的次裝置編号個數,比如:起始次裝置号為0,count=100,表示0~99的次裝置号都要綁定在同一個file_operations操作方法結構體上

*name:字元裝置名稱

當傳回值小于0,表示注冊失敗

2.2 動态配置設定一個字元裝置,注冊成功并将配置設定到的主次裝置号放入*dev裡

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
           

*dev:存放起始裝置編号的指針,當注冊成功,*dev就會等于配置設定到的起始裝置編号,可以通過MAJOR()和MINOR()函數來提取主次裝置号

baseminor:次裝置号基位址,也就是起始次裝置号

count:需要連續注冊的次裝置編号個數,比如:起始次裝置(baseminot)為0,baseminor=2,表示0~1的此裝置号都要綁定在同一個file_operations操作方法結構體上

*name:字元裝置名稱

當傳回值小于0,表示注冊失敗

2.3 初始化cdev結構體,并将file_operations結構體放入cdev->ops裡

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

其中cdev結構體的成員,如下所示:

struct cdev {
	struct kobject kobj;    //内嵌的kobject對象
	struct module *owner;    //所屬子產品
	const struct file_operations *ops;    //操作方法結構體
	struct list_head list;    //與cdev對應的字元裝置檔案的inode->i_devices的連結清單頭
	dev_t dev;    //起始裝置編号,可以通過MAJOR(),MINOR()來提取主次裝置号
	unsigned int count;    //連續注冊的次裝置号個數
};
           

2.4 将cdev結構體添加到系統中,并将dev(注冊好的裝置編号)放入cdev->dev裡,count(次裝置編号個數)放入cdev->count裡

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
           

2.5 将系統中的cdev結構體删除掉

void cdev_del(struct cdev *p)
           

2.6 登出字元裝置

void unregister_chrdev_region(dev_t from, unsigned count)
           

from:登出的指定起始裝置編号,比如:MKDEV(100, 0),表示起始主裝置号為100,起始裝置号為0

count:需要連續登出的次裝置編号個數,比如:起始次裝置号為0,baseminor=100,表示登出0~99的次裝置号

3、接下來,我們便來寫一個字元裝置驅動

裡面調用兩次上面的函數,構造兩個不同的file_operations操作結構體,

次裝置号0~1 對應 第一個file_operations,

次裝置号2 對應 第二個file_operations,

然後再/dev/目錄下,通過次裝置号(0~3)建立4個裝置節點,利用測試應用程式打開這5個檔案,看有什麼現象

3.1 驅動代碼如下:hello.c

#include <linux/module.h>	
#include <linux/kernel.h>	
#include <linux/fs.h>		
#include <linux/init.h>		
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>	
#include <asm/irq.h>
#include <asm/io.h>			
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 主裝置号 */
static int major;

static int hello_open (struct inode *inode, struct file *file)
{
	printk("hello_open\n");
	return 0;
}

static int hello2_open (struct inode *inode, struct file *file)
{
	printk("hello2_open\n");
	return 0;
}

/* 2. 構造file_operations */
//操作結構體1
static struct file_operations hello_fops = {

	.owner = THIS_MODULE,
	.open  = hello_open,
};

//操作結構體2
static struct file_operations hello2_fops = {

	.owner = THIS_MODULE,
	.open  = hello2_open,
};

#define HELLO_CNT    2	//主裝置

static struct cdev hello_cdev;	//儲存hello1_fops操作結構體的字元裝置
static struct cdev hello2_cdev; //儲存hello2_fops操作結構體的字元裝置
static struct class *cls;//class讓系統自動建立裝置節點

static int hello_init(void)
{	
	dev_t devid;
	
#if 0
	/* 一個驅動程式就占據了256個次裝置号 */
	major = register_chrdev(0, "hello", &file_operations);/* (major, 0), (major, 1), ..., (major, 255)都對應hello_fops */
#else

	/* 3. 告訴核心 */
	if (major) {
		devid = MKDEV(major, 0);
		register_chrdev_region(devid, HELLO_CNT, "hello");	/* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
	} else {
		alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
		major = MAJOR(devid);
	}

	//初始化cdev
	cdev_init(&hello_cdev, &hello_fops);
	//注冊cdev
	cdev_add(&hello_cdev, devid, HELLO_CNT);


	//測試另外一個file_operations 
	devid = MKDEV(major, 2);
	register_chrdev_region(devid, 1, "hello2");	/* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
	//初始化cdev
	cdev_init(&hello2_cdev, &hello2_fops);
	//注冊cdev
	cdev_add(&hello2_cdev, devid, 1);

#endif
	
	//建立一個類
	cls = class_create(THIS_MODULE, "hello");

	//在這個類下面再建立一個裝置(裝置節點)
	//mdev是udev的一個簡化版本
	//mdev應用程式,就會被核心調用,會根據類和類下面的裝置這些資訊
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");/* /dev/hello0 */
	class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");/* /dev/hello1 */

	//open hello2肯定打不開,因為注冊的區域(次裝置号)0~1,2用不了
	class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");/* /dev/hello2 */

	class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3");/* /dev/hello3 */

	return 0;
}

static void hello_exit(void)
{
	class_device_destroy(cls, MKDEV(major, 0));
	class_device_destroy(cls, MKDEV(major, 1));
	class_device_destroy(cls, MKDEV(major, 2));
	class_device_destroy(cls, MKDEV(major, 3));
	class_destroy(cls);
	
	cdev_del(&hello_cdev);
	unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);

	cdev_del(&hello2_cdev);
	unregister_chrdev_region(MKDEV(major, 2), 1);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");


           

3.2 測試代碼如下所示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 
 * hello_test /dev/hello0
 */

void print_usaeg(char *file)    //列印使用幫助資訊
{
	printf("%s <dev>\n", file);
}

int main(int argc, char **argv)
{
	int fd;
	if(argc != 2)
	{
		print_usaeg(argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);

	if(fd < 0)
		printf("can't open %s\n", argv[1]);
	else
		printf("can open %s\n", argv[1]);

	return 0;
}

           

4、運作測試:

如下圖,挂在驅動後,通過ls /dev/hello* -l ,看到建立了4個字元裝置節點

S3C2440 字元裝置的另一種寫法register_chrdev_region()來注冊(二十九)

接下來開始測試驅動,如下圖所示,

打開/dev/hello0時,調用的是驅動代碼的操作結構體hello_fops裡的.open(),

打開/dev/hello1時,調用的是驅動代碼的操作結構體hello_fops裡的.open(),

打開/dev/hello2時,調用的是驅動代碼的操作結構體hello2_fops裡的.open(),

打開/dev/hello3時,打開無效,因為在驅動代碼裡沒有配置設定次裝置号3的操作結構體,

S3C2440 字元裝置的另一種寫法register_chrdev_region()來注冊(二十九)

總結一下:

使用register_chrdev_region()等函數來注冊字元裝置,裡面可以存放多個不同的file_operations操作結構體,實作各種不同的功能

繼續閱讀