(LDD3实践)Chapter-3:字符驱动
2013-06-16 14:29
260 查看
SCULL的模型
首先明确一下scull设备的模型:scull设备有qset个scull_qset结构,每个scull_qset结构维护着qset个quantum,每个quantum默认是4000,而qset默认是1000,所以一个qset的大小是4000*1000个char源代码分析:
对源码的分析:http://blog.csdn.net/liuhaoyutz/article/details/7383313 这篇文章更为详细,不过我这里贴出来的是整个的源代码,可以直接运行的^ _ ^scull.h :
#ifndef _SCULL_H_ #define _SCULL_H_ #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif #ifndef SCULL_NR_DEVS #define SCULL_NR_DEVS 4 /* scull0 through scull3 */ #endif #ifndef SCULL_P_NR_DEVS #define SCULL_P_NR_DEVS 4 /* scullpipe0 through scullpipe3 */ #endif #ifndef SCULL_QUANTUM #define SCULL_QUANTUM 4000 #endif #ifndef SCULL_QSET #define SCULL_QSET 1000 #endif /* * Representation of scull quantum sets. */ struct scull_qset { void **data; struct scull_qset *next; }; struct scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; /* * The different configurable parameters */ extern int scull_major; /* main.c */ extern int scull_nr_devs; extern int scull_quantum; extern int scull_qset; int scull_trim(struct scull_dev *dev); ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); #endif /* _SCULL_H_ */
scull.c :
/* forsakening @hdu 2013/6/16 for testing ldd3 */ //#include <linux/config.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/proc_fs.h> #include <linux/fcntl.h> /* O_ACCMODE */ #include <linux/seq_file.h> #include <linux/cdev.h> #include <asm/system.h> /* cli(), *_flags */ #include <asm/uaccess.h> /* copy_*_user */ #include "scull.h" /* local definitions */ /* * Our parameters which can be set at load time. */ int scull_major = SCULL_MAJOR; int scull_minor = 0; int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */ int scull_quantum = SCULL_QUANTUM; int scull_qset = SCULL_QSET; /* insmod的输入参数 */ module_param(scull_major, int, S_IRUGO); module_param(scull_minor, int, S_IRUGO); module_param(scull_nr_devs, int, S_IRUGO); module_param(scull_quantum, int, S_IRUGO); module_param(scull_qset, int, S_IRUGO); MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet"); MODULE_LICENSE("Dual BSD/GPL"); struct scull_dev *scull_devices; /* allocated in scull_init_module */ /* * Empty out the scull device; must be called with the device * semaphore held. */ /* 删除scull设备的qset内存,删除所谓的量子集群的内存 */ int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ int i; for (dptr = dev->data; dptr; dptr = next) { /* all the list items */ if (dptr->data) { for (i = 0; i < qset; i++) kfree(dptr->data[i]); /* free一个quantum */ kfree(dptr->data); /* free一个qset */ dptr->data = NULL; } next = dptr->next; kfree(dptr); } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } /* * Open and close */ /* open和close方法 */ int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* device information */ /* 获得scull设备并保存在filp的private_data中 */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { if (down_interruptible(&dev->sem)) return -ERESTARTSYS; scull_trim(dev); /* ignore errors */ up(&dev->sem); } return 0; /* success */ } int scull_release(struct inode *inode, struct file *filp) { return 0; } /* * Follow the list */ /* scull_follow函数的作用是返回第二个参数指定的scull_qset。如果该scull_qset不存在,分配内存空间创建该scull_qset */ struct scull_qset *scull_follow(struct scull_dev *dev, int n) { struct scull_qset *qs = dev->data; /* Allocate first qset explicitly if need be */ if (! qs) { qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs == NULL) return NULL; /* Never mind */ memset(qs, 0, sizeof(struct scull_qset)); } /* Then follow the list */ while (n--) { if (!qs->next) { /* 若指定的qset无数据则初始化 */ qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs->next == NULL) return NULL; /* Never mind */ memset(qs->next, 0, sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } /* * Data management: read and write */ /* 读和写方法 */ ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; /* 在open的时候将private_data置为scull设备 */ struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* 一个qset的大小 */ int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ /* 获取信号量 */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* find listitem, qset index and offset in the quantum */ item = (long)*f_pos / itemsize; /* 属于哪个qset */ rest = (long)*f_pos % itemsize; s_pos = rest / quantum; /* 属于哪个quantum */ q_pos = rest % quantum; /* 在指定quantum上的偏移 */ /* follow the list up to the right position */ dptr = scull_follow(dev, item); /* 初始化指定的qset */ if (dptr == NULL) goto out; if (!dptr->data) { /* data是个2级指针,所以这里申请的是qset个char *的长度 */ dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if (!dptr->data[s_pos]) { /* data[i]是一级指针,填上每个quantum的首地址 */ dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } /* write only up to the end of this quantum */ /* 只在指定的quantum上面写完,若用户输入的count超过了此quantum的大小,则截断 */ if (count > quantum - q_pos) count = quantum - q_pos; /* 将用户态的数据保存在指定的qset上的quantum上 */ if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; } ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; /* the first listitem */ int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* how many bytes in the listitem */ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* 若偏移超过了scull设备的总大小则出错 */ if (*f_pos >= dev->size) goto out; if (*f_pos + count > dev->size) count = dev->size - *f_pos; /* find listitem, qset index, and offset in the quantum */ /* 找到对应的qset和quantum和在quantum上面的偏移量 */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position (defined elsewhere) */ dptr = scull_follow(dev, item); /* 定位到指定的qset */ if (dptr == NULL || !dptr->data || ! dptr->data[s_pos]) goto out; /* don't fill holes */ /* read only up to the end of this quantum */ if (count > quantum - q_pos) /* 只读取一个quantum上面的数据,即使超过了,则截断读取 */ count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { retval = -EFAULT; goto out; } *f_pos += count; /* 更新offset */ retval = count; out: up(&dev->sem); return retval; } /* scull设备对应的操作集 */ struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write, .open = scull_open, .release = scull_release, }; /* * Finally, the module stuff */ /* module部分 */ /* * The cleanup function is used to handle initialization failures as well. * Thefore, it must be careful to work correctly even if some of the items * have not been initialized */ /* 删除模块的操作,对于scull设备主要是删除内存 */ void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); /* Get rid of our char dev entries */ if (scull_devices) { for (i = 0; i < scull_nr_devs; i++) { scull_trim(scull_devices + i); cdev_del(&scull_devices[i].cdev); } kfree(scull_devices); } /* cleanup_module is never called if registering failed */ /* 调用unregister_chrdev_region删除设备号 */ unregister_chrdev_region(devno, scull_nr_devs); } /* * Set up the char_dev structure for this device. */ static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); /* 调用cdev_init和cdev_add注册一个字符设备 */ cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index); } int scull_init_module(void) { int result, i; dev_t dev = 0; /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if (scull_major) { dev = MKDEV(scull_major, scull_minor); /* 静态申请设备号 */ result = register_chrdev_region(dev, scull_nr_devs, "scull"); } else { /* 动态申请设备号 */ result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); scull_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return result; } /* * allocate the devices -- we can't have them static, as the number * can be specified at load time */ scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_devices) { result = -ENOMEM; goto fail; /* Make this more graceful */ } memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); /* Initialize each device. */ for (i = 0; i < scull_nr_devs; i++) { scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; init_MUTEX(&scull_devices[i].sem); /* 初始化信号量 */ scull_setup_cdev(&scull_devices[i], i); } return 0; /* succeed */ fail: scull_cleanup_module(); return result; } module_init(scull_init_module); module_exit(scull_cleanup_module);
scull_load脚本:
#!/bin/sh # $Id: scull_load,v 1.4 2004/11/03 06:19:49 rubini Exp $ module="scull" device="scull" mode="664" # Group: since distributions do it differently, look for wheel or use staff if grep -q '^staff:' /etc/group; then group="staff" else group="wheel" fi # invoke insmod with all arguments we got # and use a pathname, as insmod doesn't look in . by default /sbin/insmod ./$module.ko $* || exit 1 # retrieve major number major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices) # Remove stale nodes and replace them, then give gid and perms # Usually the script is shorter, it's scull that has several devices in it. rm -f /dev/${device}[0-3] mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 ln -sf ${device}0 /dev/${device} chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3]
scull_unload脚本:
#!/bin/sh module="scull" device="scull" # invoke rmmod with all arguments we got /sbin/rmmod $module $* || exit 1 # Remove stale nodes rm -f /dev/${device} /dev/${device}[0-3]
X86测试环境:
Makefile:(x86)
和上一篇日志的makefile基本无区别~~# To build modules outside of the kernel tree, we run "make" # in the kernel source tree; the Makefile these then includes this # Makefile once again. # This conditional selects whether we are being included from the # kernel Makefile or not. ifeq ($(KERNELRELEASE),) # Assume the source tree is where the running kernel was built # You should set KERNELDIR in the environment if it's elsewhere KERNELDIR ?= /lib/modules/$(shell uname -r)/build #KERNELDIR ?= /share/my_kernel/linux-2.6.30.4 # The current directory is passed to sub-makes as argument PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.bak .PHONY: modules modules_install clean else # called from kernel build system: just declare what our modules are obj-m := scull.o endif
测试结果:
[root@zx chap3_scull]# ls Makefile Module.markers modules.order Module.symvers scull.c scull.h scull_load scull_unload [root@zx chap3_scull]# make clean rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.bak [root@zx chap3_scull]# make make -C /lib/modules/2.6.27.5-117.fc10.i686/build M=/share/LDD3_code/my_module/chap3_scull modules make[1]: Entering directory `/usr/src/kernels/2.6.27.5-117.fc10.i686' CC [M] /share/LDD3_code/my_module/chap3_scull/scull.o Building modules, stage 2. MODPOST 1 modules CC /share/LDD3_code/my_module/chap3_scull/scull.mod.o LD [M] /share/LDD3_code/my_module/chap3_scull/scull.ko make[1]: Leaving directory `/usr/src/kernels/2.6.27.5-117.fc10.i686' [root@zx chap3_scull]# ./scull_load [root@zx chap3_scull]# ls /dev/scull* -l lrwxrwxrwx 1 root root 6 06-01 18:55 /dev/scull -> scull0 crw-rw-r-- 1 root wheel 248, 0 06-01 18:55 /dev/scull0 crw-rw-r-- 1 root wheel 248, 1 06-01 18:55 /dev/scull1 crw-rw-r-- 1 root wheel 248, 2 06-01 18:55 /dev/scull2 crw-rw-r-- 1 root wheel 248, 3 06-01 18:55 /dev/scull3 [root@zx chap3_scull]# free total used free shared buffers cached Mem: 512580 498320 14260 0 7856 69564 -/+ buffers/cache: 420900 91680 Swap: 1048568 13568 1035000 [root@zx chap3_scull]# cat /dev/scull0 [root@zx chap3_scull]# ls > /dev/scull0 [root@zx chap3_scull]# cat /dev/scull0 Makefile Module.markers modules.order Module.symvers scull.c scull.h scull.ko scull_load scull.mod.c scull.mod.o scull.o scull_unload [root@zx chap3_scull]# free total used free shared buffers cached Mem: 512580 498320 14260 0 7888 69564 -/+ buffers/cache: 420868 91712 Swap: 1048568 13568 1035000 [root@zx chap3_scull]#
TQ2440-ARM9测试环境:
Makefile:(arm9)
#hello_makefile ARM obj-m :=scull.o KRNELDIR :=/share/my_kernel/linux-2.6.30.4-tq2440 CROSS_COMPILE =arm-linux- CC :=$(CROSS_COMPILE)gcc LD :=$(CROSS_COMPILE)ld PWD :=$(shell pwd) all: make -C $(KRNELDIR) M=$(PWD) modules .PHONY :clean clean: rm -rf *.o *ko
运行测试:(交叉编译)
[root@zx chap3_scull]# ls Makefile Makefile-x86 Module.markers modules.order Module.symvers scull.c scull.h scull_load scull_unload [root@zx chap3_scull]# make make -C /share/my_kernel/linux-2.6.30.4-tq2440 M=/share/LDD3_code/my_module/chap3_scull modules make[1]: Entering directory `/share/my_kernel/linux-2.6.30.4-tq2440' CC [M] /share/LDD3_code/my_module/chap3_scull/scull.o Building modules, stage 2. MODPOST 1 modules CC /share/LDD3_code/my_module/chap3_scull/scull.mod.o LD [M] /share/LDD3_code/my_module/chap3_scull/scull.ko make[1]: Leaving directory `/share/my_kernel/linux-2.6.30.4-tq2440' [root@zx chap3_scull]#
运行测试:(开发板)
[root@EmbedSky /modules_test]# mkdir chap3 [root@EmbedSky /modules_test]# ls arm_hello.ko chap3 [root@EmbedSky /modules_test]# cd chap3/ [root@EmbedSky chap3]# ls [root@EmbedSky chap3]# rx scull.ko C Starting xmodem transfer. Press Ctrl+C to cancel. Transferring scull.ko... 100% 6 KB 2 KB/s 00:00:03 0 Errors 100% 6 KB 2 KB/s 00:00:03 0 Errors root@EmbedSky chap3]# rx scull_load C Starting xmodem transfer. Press Ctrl+C to cancel. Transferring scull_load... 100% 891 bytes 148 bytes/s 00:00:06 0 Errors root@EmbedSky chap3]# rx scull_unload C Starting xmodem transfer. Press Ctrl+C to cancel. Transferring scull_unload... 100% 185 bytes 37 bytes/s 00:00:05 0 Errors root@EmbedSky chap3]# chmod 777 ./* [root@EmbedSky chap3]# ./scull_load chgrp: unknown group wheel [root@EmbedSky chap3]# lsmod Not tainted scull 5028 0 - Live 0xbf050000 EmbedSky_irq 3068 1 - Live 0xbf04a000 ov9650 11088 0 - Live 0xbf042000 zd1211rw 67168 0 - Live 0xbf02c000 mac80211 144260 1 zd1211rw, Live 0xbf003000 input_polldev 3464 0 - Live 0xbf000000 [root@EmbedSky chap3]# free total used free shared buffers Mem: 60164 21184 38980 0 0 Swap: 0 0 0 Total: 60164 21184 38980 [root@EmbedSky chap3]# ls /dev/scull* -l lrwxrwxrwx 1 root root 6 Nov 14 12:22 /dev/scull -> scull0 crw-rw-r-- 1 root root 252, 0 Nov 14 12:22 /dev/scull0 crw-rw-r-- 1 root root 252, 1 Nov 14 12:22 /dev/scull1 crw-rw-r-- 1 root root 252, 2 Nov 14 12:22 /dev/scull2 crw-rw-r-- 1 root root 252, 3 Nov 14 12:22 /dev/scull3 [root@EmbedSky chap3]# ls > /dev/scull0 [root@EmbedSky chap3]# cat /dev/scull0 scull.ko scull_load scull_unload [root@EmbedSky chap3]# free total used free shared buffers Mem: 60164 21184 38980 0 0 Swap: 0 0 0 Total: 60164 21184 38980 [root@EmbedSky chap3]# lsmod > /dev/scull0 [root@EmbedSky chap3]# cat /dev/scull0 Not tainted scull 5028 2 - Live 0xbf050000 EmbedSky_irq 3068 1 - Live 0xbf04a000 ov9650 11088 0 - Live 0xbf042000 zd1211rw 67168 0 - Live 0xbf02c000 mac80211 144260 1 zd1211rw, Live 0xbf003000 input_polldev 3464 0 - Live 0xbf000000 [root@EmbedSky chap3]# free total used free shared buffers Mem: 60164 21184 38980 0 0 Swap: 0 0 0
//add at 6/16
用户态测试scull:
/* forsakening @hdu 2013/6/16 fot testing scull */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /* open */ #include <unistd.h> /* write */ #include <stdlib.h> /* malloc */ #include <stdio.h> int main(int argc, char *argv[]) { int fd = open("/dev/scull0", O_APPEND); if (!fd) { printf("open error!\n"); return 1; } else printf("open fd is: %d\n", fd); /* if (!write(fd, "This is from user!", 1000)) { printf("write error!\n"); return 1; } */ /* scull设备在读取的时候一次最多只能读取1000.cat命令是循环实现的? */ char *read_buf = malloc(1000); int read_length = 0; if ((read_length = read(fd, read_buf, 1000))) { printf("This is from kernel!\n--------------\n"); printf("%s-----------\nread_length:%d\n", read_buf, read_length); } else { printf("read error!\n"); free(read_buf); } free(read_buf); return 0; }
PC测试:
[root@zx chap3_scull]# gcc -Wall -o user-scull user-scull.c [root@zx chap3_scull]# echo '' > /dev/scull [root@zx chap3_scull]# ./user-scull open fd is: 3 This is from kernel! -------------- ----------- read_length:1 [root@zx chap3_scull]# echo 'forsakening' > /dev/scull [root@zx chap3_scull]# ./user-scull open fd is: 3 This is from kernel! -------------- forsakening ----------- read_length:12 [root@zx chap3_scull]# cat /dev/scull forsakening [root@zx chap3_scull]#
ARM测试:
在pc上执行arm-linux-gcc -Wall -o user-scull user-scull.c 得到arm开发板的可执行文件[root@EmbedSky chap3]# ls
scull.ko scull_load scull_unload
[root@EmbedSky chap3]# lsmod
Not tainted
EmbedSky_irq 3068 1 - Live 0xbf04a000
ov9650 11088 0 - Live 0xbf042000
zd1211rw 67168 0 - Live 0xbf02c000
mac80211 144260 1 zd1211rw, Live 0xbf003000
input_polldev 3464 0 - Live 0xbf000000
[root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]# ./scull_load
chgrp: unknown group wheel
[root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]# rx user-scull
CC
Starting xmodem transfer. Press Ctrl+C to cancel.
Transferring user-scull...
100% 6 KB 1 KB/s 00:00:05 0 Errors
root@EmbedSky chap3]#
[root@EmbedSky chap3]#
[root@EmbedSky chap3]# ls
scull.ko scull_load scull_unload user-scull
[root@EmbedSky chap3]# chmod 777 ./*
[root@EmbedSky chap3]# ./user-scull
open fd is: 3
read error!
*** glibc detected *** ./user-scull: double free or corruption (top): 0x00011008 ***
Aborted
[root@EmbedSky chap3]# ls > /dev/scull
[root@EmbedSky chap3]# ./user-scull
open fd is: 3
This is from kernel!
--------------
scull.ko
scull_load
scull_unload
user-scull
-----------
read_length:44
[root@EmbedSky chap3]#
相关文章推荐
- (LDD3读书记录)Chapter-3:字符驱动
- s3c2440基于linux的gpio led字符设备驱动实践
- s3c2440基于linux的gpio led字符设备驱动实践
- LDD3《Linux设备驱动》中的最简单的字符设备驱动实现与测试
- LDD3 笔记: 第3章 字符设备的驱动
- 学习Ldd3--字符设备驱动(第三章)
- ldd3代码分析(高级字符驱动)
- 字符设备驱动基础篇4——字符设备驱动读写接口的操作实践
- linux字符驱动学习实践1(简单控制LED灯)
- s3c2440基于linux的gpio led字符设备驱动实践 [转]
- LDD3字符设备驱动pipe提示No such device or address
- LDD3 字符设备驱动
- ldd3 chapter3 字符驱动简单记录
- ldd3学习之三: 字符驱动
- 字符设备驱动相关函数及数据结构简介 (ldd3)
- ldd3笔记_字符设备驱动
- LDD3笔记:第三章 字符设备驱动
- tony之linux driver_LDD3_scull字符设备驱动编译在新内核编译问题
- 字符设备驱动相关函数及数据结构简介 (ldd3)
- ldd3 源码编译之 scullc 字符设备驱动 错误解决办法