Haben Sie sich jemals gefragt, wie Sie unter Linux einen Treiber für Ihr Charaktergerät schreiben können? Haben Sie schon einmal darüber nachgedacht, wie Sie Ihren Treiber in die Lage versetzen können, einige grundlegende Funktionen in einem Linux-System zu implementieren, wie z. B. Öffnen, Schließen, Lesen, Schreiben, Steuern usw.? Haben Sie jemals darüber nachgedacht, wie Sie Ihren Treiber in die Lage versetzen können, einige erweiterte Funktionen in Linux-Systemen zu implementieren, wie z. B. asynchrone Benachrichtigung, Multiplexing, Speicherzuordnung usw.? Wenn Sie sich für diese Probleme interessieren, stellt Ihnen dieser Artikel eine effektive Methode zum Erreichen dieser Ziele vor: Linux-Gerätetreiber-Zeichengeräte. Ein Zeichengerät ist eine Datenstruktur, die zur Beschreibung von Geräten mit sequentiellem Zugriff verwendet wird. Sie ermöglicht die einfache und einheitliche Übergabe der Informationen und Attribute eines Zeichengeräts an den Kernel und ermöglicht so die Geräteidentifizierung und den Treiber. Zeichengeräte sind auch ein Mechanismus zur Implementierung grundlegender Funktionen. Sie ermöglichen die standardmäßige und universelle Definition und Verwendung verschiedener Zeichengeräteoperationen und ermöglichen so das Öffnen, Schließen, Lesen, Schreiben, Steuern usw. Funktion. Zeichengeräte sind auch ein Rahmen für die Implementierung erweiterter Funktionen. Sie ermöglichen die flexible und erweiterbare Definition und Verwendung verschiedener Zeichengeräteschnittstellen und -protokolle, um asynchrone Benachrichtigungen, Multiplexing, Speicherzuordnung und andere Funktionen zu erreichen. In diesem Artikel werden die Anwendung und Rolle von Zeichengeräten in Linux-Gerätetreibern anhand der Grundkonzepte, Grammatikregeln, Schreibmethoden, Registrierungsprozesse, Betriebsmethoden usw. von Zeichengeräten ausführlich vorgestellt, um Ihnen bei der Beherrschung dieser nützlichen und leistungsstarken Methode zu helfen.
Zeichengerät ist der einfachere Gerätetyp unter den drei Hauptgerätetypen (Zeichengerät, Blockgerät und Netzwerkgerät). Die Hauptarbeit in seinem Treiber besteht darin, die CDev-Struktur zu initialisieren, hinzuzufügen und zu löschen, sie zu beantragen und freizugeben Gerätenummer. Neben dem Ausfüllen der Betriebsfunktionen in der file_operations-Struktur ist die Implementierung von Funktionen wie read(), write() und ioctl() in der file_operations-Struktur die Hauptarbeit des Treiberdesigns.
/* * 虚拟字符设备globalmem实例: * 在globalmem字符设备驱动中会分配一片大小为 GLOBALMEM_SIZE(4KB) * 的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过 * Linux系统调用访问这片内存。 */ #include #include #include #include #include #include #include #include #include #include #include #define DEV_NAME "globalmem" /* /dev中显示的设备名 */ #define DEV_MAJOR 0 /* 指定主设备号,为0则动态获取 */ /* ioctl用的控制字 */ #define GLOBALMEM_MAGIC 'M' #define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0) /*--------------------------------------------------------------------- local vars */ /*globalmem设备结构体*/ typedef struct { struct cdev cdev; /* 字符设备cdev结构体*/ #define MEM_SIZE 0x1000 /*全局内存最大4K字节*/ unsigned char mem[MEM_SIZE]; /*全局内存*/ struct semaphore sem; /*并发控制用的信号量*/ } globalmem_dev_t; static int globalmem_major = DEV_MAJOR; static globalmem_dev_t *globalmem_devp; /*设备结构体指针*/ /*--------------------------------------------------------------------- file operations */ /*文件打开函数*/ static int globalmem_open(struct inode *inodep, struct file *filep) { /* 获取dev指针 */ globalmem_dev_t *dev = container_of(inodep->i_cdev, globalmem_dev_t, cdev); filep->private_data = dev; return 0; } /*文件释放函数*/ static int globalmem_release(struct inode *inodep, struct file *filep) { return 0; } /*读函数*/ static ssize_t globalmem_read(struct file *filep, char __user *buf, size_t len, loff_t *ppos) { globalmem_dev_t *dev = filep->private_data; unsigned long p = *ppos; int ret = 0; /*分析和获取有效的长度*/ if (p > MEM_SIZE) { printk(KERN_EMERG "%s: overflow!\n", __func__); return - ENOMEM; } if (len > MEM_SIZE - p) { len = MEM_SIZE - p; } if (down_interruptible(&dev->sem)) /* 获得信号量*/ return - ERESTARTSYS; /*内核空间->用户空间*/ if (copy_to_user(buf, (void*)(dev->mem + p), len)) { ret = - EFAULT; }else{ *ppos += len; printk(KERN_INFO "%s: read %d bytes from %d\n", DEV_NAME, (int)len, (int)p); ret = len; } up(&dev->sem); /* 释放信号量*/ return ret; } /*写函数*/ static ssize_t globalmem_write(struct file *filep, const char __user *buf, size_t len, loff_t *ppos) { globalmem_dev_t *dev = filep->private_data; int ret = 0; unsigned long p = *ppos; if (p > MEM_SIZE) { printk(KERN_EMERG "%s: overflow!\n", __func__); return - ENOMEM; } if (len > MEM_SIZE - p) { len = MEM_SIZE - p; } if (down_interruptible(&dev->sem)) /* 获得信号量*/ return - ERESTARTSYS; /*用户空间->内核空间*/ if (copy_from_user(dev->mem + p, buf, len)) { ret = - EFAULT; }else{ *ppos += len; printk(KERN_INFO "%s: written %d bytes from %d\n", DEV_NAME, (int)len, (int)p); ret = len; } up(&dev->sem); /* 释放信号量*/ return ret; } /* seek文件定位函数 */ static loff_t globalmem_llseek(struct file *filep, loff_t offset, int start) { globalmem_dev_t *dev = filep->private_data; int ret = 0; if (down_interruptible(&dev->sem)) /* 获得信号量*/ return - ERESTARTSYS; switch (start) { case SEEK_SET: if (offset MEM_SIZE) { printk(KERN_EMERG "%s: overflow!\n", __func__); return - ENOMEM; } ret = filep->f_pos = offset; break; case SEEK_CUR: if ((filep->f_pos + offset) f_pos + offset) > MEM_SIZE) { printk(KERN_EMERG "%s: overflow!\n", __func__); return - ENOMEM; } ret = filep->f_pos += offset; break; default: return - EINVAL; break; } up(&dev->sem); /* 释放信号量*/ printk(KERN_INFO "%s: set cur to %d.\n", DEV_NAME, ret); return ret; } /* ioctl设备控制函数 */ static long globalmem_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { globalmem_dev_t *dev = filep->private_data; switch (cmd) { case MEM_CLEAR: if (down_interruptible(&dev->sem)) /* 获得信号量*/ return - ERESTARTSYS; memset(dev->mem, 0, MEM_SIZE); up(&dev->sem); /* 释放信号量*/ printk(KERN_INFO "%s: clear.\n", DEV_NAME); break; default: return - EINVAL; } return 0; } /*文件操作结构体*/ static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .open = globalmem_open, .release = globalmem_release, .read = globalmem_read, .write = globalmem_write, .llseek = globalmem_llseek, .compat_ioctl = globalmem_ioctl }; /*---------------------------------------------------------------------*/ /*初始化并注册cdev*/ static int globalmem_setup(globalmem_dev_t *dev, int minor) { int ret = 0; dev_t devno = MKDEV(globalmem_major, minor); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; ret = cdev_add(&dev->cdev, devno, 1); if (ret) { printk(KERN_NOTICE "%s: Error %d dev %d.\n", DEV_NAME, ret, minor); } return ret; } /*设备驱动模块加载函数*/ static int __init globalmem_init(void) { int ret = 0; dev_t devno; /* 申请设备号*/ if(globalmem_major){ devno = MKDEV(globalmem_major, 0); ret = register_chrdev_region(devno, 2, DEV_NAME); }else{ /* 动态申请设备号 */ ret = alloc_chrdev_region(&devno, 0, 2, DEV_NAME); globalmem_major = MAJOR(devno); } if (ret return ret; } /* 动态申请设备结构体的内存,创建两个设备 */ globalmem_devp = kmalloc(2*sizeof(globalmem_dev_t), GFP_KERNEL); if (!globalmem_devp) { unregister_chrdev_region(devno, 2); return - ENOMEM; } ret |= globalmem_setup(&globalmem_devp[0], 0); /* globalmem0 */ ret |= globalmem_setup(&globalmem_devp[1], 1); /* globalmem1 */ if(ret) return ret; init_MUTEX(&globalmem_devp[0].sem); /*初始化信号量*/ init_MUTEX(&globalmem_devp[1].sem); printk(KERN_INFO "globalmem: up %d,%d.\n", MAJOR(devno), MINOR(devno)); return 0; } /*模块卸载函数*/ static void __exit globalmem_exit(void) { cdev_del(&globalmem_devp[0].cdev); cdev_del(&globalmem_devp[1].cdev); kfree(globalmem_devp); unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); printk(KERN_INFO "globalmem: down.\n"); } /* 定义参数 */ module_param(globalmem_major, int, S_IRUGO); module_init(globalmem_init); module_exit(globalmem_exit); /* 模块描述及声明 */ MODULE_AUTHOR("Archie Xie "); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("A char device module just for demo."); MODULE_ALIAS("cdev gmem"); MODULE_VERSION("1.0");
Wechseln Sie zum Root-Benutzer
Modul einfügen
insmod globalmem.ko
Geräteknoten erstellen (die nachfolgenden Routinen zeigen, wie Knoten automatisch erstellt werden)
cat /proc/devices 找到主设备号major mknod /dev/globalmem0 c major 0 和 /dev/globalmem1 c major 1
Lese- und Schreibtest
echo "hello world" > /dev/globalmem cat /dev/globalmem
Durch diesen Artikel verstehen wir die Anwendung und Rolle von Zeichengeräten in Linux-Gerätetreibern und lernen, wie man Zeichengeräte schreibt, registriert, bedient, ändert und debuggt. Wir haben festgestellt, dass Zeichengeräte eine sehr geeignete Methode für die Entwicklung eingebetteter Systeme sind. Sie ermöglichen uns die einfache Beschreibung und Verwaltung von Geräten mit sequentiellem Zugriff sowie die Implementierung grundlegender und erweiterter Funktionen. Natürlich gibt es auch bei Zeichengeräten einige Vorsichtsmaßnahmen und Einschränkungen, z. B. die Notwendigkeit, Syntaxspezifikationen einzuhalten, auf Berechtigungsprobleme zu achten, auf Leistungseinbußen zu achten usw. Daher müssen wir bei der Verwendung von Zeichengeräten über bestimmte Hardware-Kenntnisse und -Erfahrung sowie gute Programmiergewohnheiten und Debugging-Fähigkeiten verfügen. Ich hoffe, dieser Artikel kann Ihnen einen Einstiegsleitfaden bieten und Ihnen ein erstes Verständnis der Zeichengeräte vermitteln. Wenn Sie mehr über Zeichengeräte erfahren möchten, empfiehlt es sich, auf weitere Materialien und Beispiele zurückzugreifen sowie selbst zu üben und zu erkunden.
Das obige ist der detaillierte Inhalt vonLinux-Gerätetreiber-Zeichengerät: eine bequeme Möglichkeit, Geräte mit sequentiellem Zugriff zu beschreiben und zu verwalten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!