Schreiben von Zeichengerätetreibern

1. Schreiben und Debuggen

(1) Experiment 1

Linux-Gerätetreiber können in zwei Formen definiert werden: Eine davon besteht aus globalen statischen Variablen und die andere verwendet die vom Kernel bereitgestellte API. Die zweite Methode wird hier verwendet, um einen einfachen virtuellen Gerätetreiber zu implementieren und ihn zu implementieren. Lese- und Schreibfunktionen.

Kernelmoduscode device_drive.c:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/cdev.h>
 
#define DEMO_NAME "my_demo_dev"
 	
static dev_t dev;   //设备号,dev_t封装的是u32类型,该类型前12位表示主设备号,后20位表示次设备号
static struct cdev *demo_cdev;	//字符设备
static signed count = 1;
 
 
//在demodrv_open操作中打印主次设备号
static int demodrv_open(struct inode *inode, struct file *file)
{   
    //Linux内核提供的读取主设备号和次设备号的方法
	int major = MAJOR(inode->i_rdev);   
	int minor = MINOR(inode->i_rdev);
 
	printk("%s: major=%d, minor=%d\n",__func__,major,minor);  //__func__宏获取当前的函数名
 
	return 0;
}


//在demodrv_read和demodrv_write中仅打印函数名
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos)
{
	printk("%s enter\n",__func__);   //打印函数名
	
	return 0;
}
 
 
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos)
{
	printk("%s enter\n",__func__);
	
	return 0;
}
 
 //给设备的操作,和在文件系统中使用的是相同的结构体
static const struct file_operations demodrv_fops = {
	.owner = THIS_MODULE,
	.open = demodrv_open,
	.read = demodrv_read,
	.write = demodrv_write
};
 
 
static int __init simple_char_init(void)
{
	int ret;
 
	ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME);		//动态申请设备号
	if(ret)
	{
		printk("failed to allocate char device region\n");
		return ret;
	}
	demo_cdev = cdev_alloc();   //分配空间
	if(!demo_cdev) 
	{
		printk("cdev_alloc failed\n");
		goto unregister_chrdev;
	}
 
	cdev_init(demo_cdev,&demodrv_fops);		//cdev_init初始化cdev,如果定义的是struct cdev结构体而不是指针类型,只需要执行cdev_init()就可以了
 
	ret = cdev_add(demo_cdev,dev,count);	//把这个设备添加到系统中
	if(ret)
	{
		printk("cdev_add failed\n");
		goto cdev_fail;
	}
 
	printk("successed register char device: %s\n",DEMO_NAME);
	printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));
 
	return 0;
 
cdev_fail:
	cdev_del(demo_cdev);
 
unregister_chrdev:
	unregister_chrdev_region(dev,count);
 
	return ret;
} 
 
 
static void __exit simple_char_exit(void)
{
	printk("removing device\n");
 
	if(demo_cdev)
		cdev_del(demo_cdev);
 
	unregister_chrdev_region(dev,count);
}
 
module_init(simple_char_init);
module_exit(simple_char_exit);
 
MODULE_LICENSE("GPL");

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

Darüber hinaus muss das generierte Gerät den entsprechenden Knoten im Verzeichnis /dev generieren. Er muss manuell generiert werden. Verwenden Sie den Befehl mknod und führen Sie den Befehl sudo mknod /dev/demo_drv c 240 0 aus. c stellt das Zeichengerät 240 dar ist die Hauptgerätenummer und 0 ist diese Gerätenummer. Führen Sie dann den Befehl ll /dev aus, um den Status des Dev-Verzeichnisses anzuzeigen:

Fügen Sie hier eine Bildbeschreibung ein

Verwenden Sie als Nächstes das User-Space-Testprogramm, um diesen Zeichengerätetreiber zu testen:

Der test.c-Code lautet wie folgt:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
 
#define DEMO_DEV_NAME "/dev/demo_drv"		//定义设备的路径
 
int main() 
{
	char buffer[64];
	int fd;
 
	fd = open(DEMO_DEV_NAME,O_RDONLY);		//O_RDONLY: 以只读的方式打开
	if(fd<0) 
	{
		printf("open device %s failed\n",DEMO_DEV_NAME);
		return -1;
	}
 
	read(fd,buffer,64);
	close(fd);
 
	return 0;
}

Führen Sie das Testprogramm aus und drucken Sie Kernel-Meldungen:

Fügen Sie hier eine Bildbeschreibung ein

Habe die Open- und Read-Methoden ausgedruckt

(2) Experiment 2

Zeichengerätetreiber können auch mit dem Misc-Mechanismus registriert werden. Das heißt, Linux unterteilt einige Zeichengeräte, die die vorgegebenen Anforderungen nicht erfüllen, in verschiedene Geräte. Die Hauptgeräteanzahl solcher Geräte beträgt 10. Die Miscdevice-Struktur wird im Kernel dazu verwendet Beschreiben Sie es. Wenn Sie ein Gerät mit dem Misc-Mechanismus erstellen möchten, müssen Sie die Miscdevice-Struktur definieren.

Kernelmodul drive2.c:

# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
//加入misc机制
# include <linux/miscdevice.h>
# include <linux/kfifo.h>
 
DEFINE_KFIFO(mydemo_fifo,char,64);	//定义和初始化一个fifo
 
//设备名
# define DEMO_NAME "my_demo_dev"
 
static struct device *mydemodrv_device;
 
static int demodrv_open(struct inode *inode, struct file *file)
{
	int major = MAJOR(inode->i_rdev);
	int minor = MINOR(inode->i_rdev);
 
	printk("%s: major=%d, minor=%d\n",__func__,major,minor);
 
	return 0;
}
 
 
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
	int actual_readed;
	int ret;
 
	ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed);
	if(ret)
		return -EIO;
 
	printk("%s,actual_readed=%d,pos=%lld\n",__func__,actual_readed,*ppos);
 
	return actual_readed;
}
 
 
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos)
{
	unsigned int actual_write;
	int ret;
 
	ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write);
	if(ret)
		return -EIO;
 
	printk("%s: actual_write=%d,ppos=%lld\n",__func__,actual_write,*ppos);
 
	return actual_write;
}
 
 
static const struct file_operations demodrv_fops = {
	.owner = THIS_MODULE,
	.open = demodrv_open,
	.read = demodrv_read,
	.write = demodrv_write,
};
 
static struct miscdevice mydemodrv_misc_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEMO_NAME,
	.fops = &demodrv_fops,  //设备相应的操作
};
 
static int __init simple_char_init(void)
{
	int ret;
 
	ret = misc_register(&mydemodrv_misc_device);	//misc_register()函数来注册,可以自动创建设备结点,不需要mknod来手动创建设备节点,传入的参数是定义的miscdevice结构体的地址
	if(ret)
	{
		printk("failed register misc device\n");
		return ret;
	}
 
	mydemodrv_device = mydemodrv_misc_device.this_device;
 
	printk("successed register char device: %s\n",DEMO_NAME);
 
	return 0;
} 
 
 
static void __exit simple_char_exit(void)
{
	printk("removing device\n");
 
	misc_deregister(&mydemodrv_misc_device);
}
 
module_init(simple_char_init);
module_exit(simple_char_exit);
 
MODULE_LICENSE("GPL");

Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

Sehen Sie sich das Dev-Verzeichnis an:

Fügen Sie hier eine Bildbeschreibung ein

Benutzermodus-Testprogramm test.c:

# include <stdio.h>
# include <fcntl.h>
# include <unistd.h>
# include <malloc.h>
# include <string.h>
 
# define DEMO_DEV_NAME "/dev/my_demo_dev"	//设备路径
 
int main() 
{
	char buffer[64];
	int fd;
	int ret;
	size_t len;
 
	char message[] = "hello";
	char *read_buffer;
 
	len = sizeof(message);
 
	fd = open(DEMO_DEV_NAME,O_RDWR);
	if(fd<0) 
	{
		printf("open device %s failed\n",DEMO_DEV_NAME);
		return -1;
	}
 
	ret = write(fd,message,len);	//向设备写数据
	if(ret != len)
	{
		printf("cannot write on device %d,ret=%d\n",fd,ret);
		return -1;
	}
 
	read_buffer = malloc(2*len);
	memset(read_buffer,0,2*len);	//memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。
	ret = read(fd,read_buffer,2*len);	//把数据从设备读到read_buffer
	printf("read %d bytes\n",ret);
	printf("read buffer=%s\n",read_buffer);
 
	close(fd);
 
	return 0;
}

Führen Sie dieses Testprogramm aus:

Fügen Sie hier eine Bildbeschreibung ein

Kernel-Meldungen drucken:

Fügen Sie hier eine Bildbeschreibung ein

2. Lernen des Quellcodes

//在linux内核中,字符设备是由cdev结构体来描述的
struct cdev {
	struct kobject kobj;
	struct module *owner;	//所属模块
	const struct file_operations *ops;		//文件操作结构体
	struct list_head list;		//list字段将所有的字符设备组织成一个链表
	dev_t dev;		//dev是字符设备的设备号,包括主设备号和次设备号
	unsigned int count;		//count字段是同一个主设备号中次设备号的个数
} __randomize_layout;
#define MINORBITS	20		//次设备号的位数是20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))	//读取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))	//读取次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))		//生成设备号
static struct char_device_struct {
	struct char_device_struct *next;	//next成员实现这些结构体之间的关联,在系统中主要是完成该类型变量的链接
	unsigned int major;		//major表示字符设备的主设备号
	unsigned int baseminor;		//baseminor表示起始次设备号
	int minorct;		//minorct表示该字符设备支持的最大次设备个数
	char name[64];
	struct cdev *cdev;		/* will die */	//cdev指针指向该字符设备对应的设备抽象结构体变量
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

char_device_struct implementiert die Verwaltung registrierter Zeichengeräteintervalle und schließt die Zuordnung mit cdev ab

cdev wird hauptsächlich verwendet, um die Referenzzählung des cdev und seine Zuordnung zu sysfs zu implementieren.

3. Fragen und Antworten

1. Erklären Sie Gerätetreiber bzw. Dateisysteme.

​ Der Gerätetreiber ist die erste Abstraktionsebene, die dreidimensionale Daten wie Zylinder, Spuren und Sektoren in Blöcke in einem eindimensionalen linearen Adressraum umwandelt.

Das Dateisystem ist eine zweite Abstraktionsebene, die auf der ersten Abstraktionsebene basiert, d. h. Blöcke werden abstrahiert und in einem Dateisystem organisiert

2. Erklären Sie ausgehend von den Systemaufrufen open(), read() und write() der Anwendung, wie man im Kernel gefangen wird.

Fügen Sie hier eine Bildbeschreibung ein

3.Was macht die file_operations()-Struktur?

Die file_operations-Struktur schließt das Lesen, Schreiben, Speichern usw. des Geräts ab. Diese Vorgänge werden alle von diesen in der file_operations-Struktur gespeicherten Funktionszeigern verarbeitet. Die Funktionen, auf die diese Funktionszeiger zeigen, müssen im Treibermodul implementiert werden.

struct file_operations {
	struct module *owner;    //拥有该结构的模块的指针,一般为THIS_MODULES
	......
	int (*mmap) (struct file *, struct vm_area_struct *);    //用于请求将设备内存映射到进程地址空间
	......
	int (*open) (struct inode *, struct file *);    //打开
	......
}

4. Teilen Sie uns von open(), read(), write() der Anwendung bis zum Aufruf der entsprechenden Funktion in file_operations() Ihr Verständnis der folgenden Abbildung mit:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

5.Welche Rolle spielt die Gerätenummer im Treiber? Warum brauchen wir eine Hauptgerätenummer und eine Nebengerätenummer?

Jede Datei ist ein Gerät und die Gerätenummer besteht aus einer Hauptgerätenummer und einer Nebengerätenummer.

Hauptgerätenummer:

  1. Es wird allgemein angenommen, dass eine Hauptgerätenummer einem Treiber entspricht
  2. Ein Treiber kann mehrere Geräte verwalten, d. h. eine Hauptgerätenummer entspricht mehreren Geräten
  3. Eine Hauptgerätenummer kann auch mehreren Treibern entsprechen

Nebengerätenummer:

  1. Eine Nebengerätenummer entspricht einem Gerät

Ein Treiber kann mehrere Geräte dieses Typs verwalten, und die Anzahl der Geräte kann 2 hoch 20 betragen. Der Grund dafür ist, dass die Nebengerätenummer 20 Ziffern hat, es aber unmöglich ist, tatsächlich so viele Geräte zu haben.

6. Welche An- und Abmeldefunktionen hat der Fahrer? Warum müssen Sie sich anmelden und stornieren?

Registrierte Funktionen für Zeichengerätetreiber:

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}
/*__register_chrdev() - 创建并注册一个占有较小大小的字符设备
  major:主设备号,当用户设置为0时,内核会动态分配一个设备号。
  baseminor: 次设备号,要在一定范围内从0开始
  count: 次设备号的范围
  name: 设备名称
  fops: 文件系统的接口指针
  
  如果major == 0,此函数将动态分配一个major并返回它的编号。
  如果major > 0,此函数将尝试使用给定的主设备号来保留设备,成功时将返回0。
  
  失败时返回-ve errno。
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,
		      unsigned int count, const char *name,
		      const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, baseminor, count, name);	//注册一个主设备号和一个一定范围内具体的次设备号
	if (IS_ERR(cd))
		return PTR_ERR(cd);

	cdev = cdev_alloc();	//分配空间
	if (!cdev)
		goto out2;
	//对cdev进行赋值操作
	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);

	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);	//把这个设备添加到系统中
	if (err)
		goto out;

	cd->cdev = cdev;	//完成cd与cdev的关联

	return major ? 0 : cd->major;
out:
	kobject_put(&cdev->kobj);
out2:
	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
	return err;
}

Funktion zur Aufhebung der Registrierung für den Zeichengerätetreiber:

static inline void unregister_chrdev(unsigned int major, const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
			 unsigned int count, const char *name)
{
	struct char_device_struct *cd;

	cd = __unregister_chrdev_region(major, baseminor, count);
	if (cd && cd->cdev)
		cdev_del(cd->cdev);
	kfree(cd);
}

Durch die Registrierung wird der entsprechende Gerätetreiber zur Verwendung generiert

Durch das Abmelden wird Speicher freigegeben, damit andere Prozesse ausgeführt werden können.

Supongo que te gusta

Origin blog.csdn.net/qq_58538265/article/details/133915966
Recomendado
Clasificación