1:linux字符設備及udev
1.1字符設備
字符設備就是:一個一個字節來進行訪問的,不能對字符設備進行隨機讀寫。簡單字符設備創建實例如下:
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #define LED_MAJOR 241 /*預設的LED_MAJOR的主設備號*/
- static int led_major = LED_MAJOR;
- volatile unsigned long *gpbcon = NULL ;
- volatile unsigned long *gpbdat = NULL ;
- volatile unsigned long *gpbup = NULL ;
- struct cdev cdev; /*cdev結構體*/
- static int led_drv_open(struct inode *inode, struct file *file)
- {
- printk("first_drv_open\n");
- return 0;
- }
- static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
- {
- return 0;
- }
- static struct file_operations led_drv_fops = {
- .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
- .open = led_drv_open,
- .write = led_drv_write,
- };
- /*初始化並注冊cdev*/
- static void led_setup_cdev(void)
- {
- int err, devno = MKDEV(led_major, 0);//index 為從設備號
- cdev_init(&cdev, &led_drv_fops);
- cdev.owner = THIS_MODULE;
- cdev.ops = &led_drv_fops;
- err = cdev_add(&cdev, devno, 1);//devno 為第一個設備號,1為數量
- if (err)
- printk(KERN_NOTICE "Error %d adding", err);
- }
- static int led_drv_init(void)
- {
- int result;
- dev_t devno;
- devno=MKDEV(led_major,0);
- if(led_major)//靜態申請設備號
- result=register_chrdev_region(devno,1,"led1_dev");
- else
- {
- result = alloc_chrdev_region(&devno,0,1,"led1_dev");//動態申請設備號
- led_major = MAJOR(devno);
- }
- if(result<0)
- {
- printk (KERN_WARNING "hello: can't get major number %d\n", led_major);
- return result;
- }
- led_setup_cdev();
- return 0;
- }
- static void led_drv_exit(void)
- {
- cdev_del(&cdev); // 注銷cdev
- unregister_chrdev_region(MKDEV(led_major,0),1);//釋放設備號
- }
- module_init(led_drv_init);
- module_exit(led_drv_exit);
- MODULE_LICENSE("GPL");
上面是一個字符設備的框架,沒有實質性的東西,如:沒有實現file_operations結構中的成員函數,同時led_drv_open(),led_drv_write()沒有做任何東西。
現在將創建一個基本的字符設備過程總結如下:
C:初始化並關聯file_operations結構體。 cdev_init(&cdev, &led_drv_fops);其中file_operations結構體中主要定義對字符設備的操作函數。
D:添加字符設備到內核。int cdev_add(struct cdev *p, dev_t dev, unsigned count),
E:移除字符設備及設備號。 cdev_del(&cdev); unregister_chrdev_region(MKDEV(led_major,0),1);完成於cdev_add()和register_chrdev_region()相反的工作
上面涉及到的API可以在函數linux/fs/char_dev.c中找到定義。
按照上面的方法可以創建一個主設備號為:241,次設備號為:0的一個字符設備。
可以講上面的代碼編譯進內核:.
其方式如下:
將上面的代碼保存到test.c中
在[root@centos /opt/FriendlyARM/linux-2.6.32.2/drivers]$ 目下下面建立文件件 mkdir suiyuan
之后在suiyuan中創建makefile,將上面的代碼直接編譯進內核,因此可以不需要kconfig文件
其makefile的內容為:
obj-$(CONFIG_SUIYUAN_HELLOWORLD)+= suiyuan_hello_module.o
#obj-$(CONFIG_STATIC_SUIYUAN_HELLOWORLD) += static_suiyuan_hello_module.o
obj-m+= static_suiyuan_hello_module.o
obj-y+= class_device_driver.o
obj-y+= leds.o
obj-y+= test.o
#obj-y+= ledsv2.o
#obj-y+= ledsv3.o
表示將obj-y表示將test編譯進內核,在加載內的時候將該字符設備加載進內核。
同時在再修改/opt/FriendlyARM/linux-2.6.32.2/drivers目錄下面的Makefile文件,在Makefile文件的末尾添加obj-y += suiyuan/ 表示對suiyuan目錄下面的所有文件進行編譯。
有上面可以知道kconfig文件費必須,同時只有在需要將內核編譯為模塊的時候,才需要編寫配置文件kconfig。
重新編譯內核,將編譯好的內核轉化為uImage 格式。
對於上面創建的字符設備在使用的時候需要,創建設備文件。創建設備文件使用命令:mknod
mknod的使用方式如下:
定義:mknod - make block or character special files
語法:mknod /dev/ttyUSBn c Major Minor
1,n要等於次設備號Minor,且要小於主設備號Major.
2, c:面向字符設備(b:面向塊設備,如:磁盤、軟盤、磁帶;其他設備基本都為字符設備).
在上面的代碼中,我們的主從設備號為:241和0。故創建設備節點:mknod /dev/suiyuan c 241 0
之后可以[@mini2440 /dev]#cat test
led_drv_open
cat: read error: Invalid argument
如上面的結果,表示應用程序cat在訪問設備文件的時候,調用了file_operations 數據結構中的open()函數。應用程序和底層驅動就此聯系了起來。
1.2 udev是什么及其作用
udev文件系統是針對2.6內核,提供一個基於用戶空間的動態設備節點管理和命名的使用程序,udev 以守護進程的形式運行,通過偵聽內核發出來的 uevent 來動態的管理/dev
目錄下的設備文件。:udev是一種工具,它能夠根據系統中的硬件設備的狀態動態更新設備文件,包括設備文件的創建,刪除等。設備文件通常放在/dev目錄下。使用udev后,在/dev目錄下就只包含系統中真正存在的設備。udev動態的根據根文件系統目錄下面的/sys文件主動的創建設備文件,故在編寫好設備驅動的時候,不需要再手動通過命令mknod來創建設備文件。udev涉及到/sys目錄下面的文件,而/sys涉及到linux設備驅動模式。以后自己在總結這方面的東西。udev是一個用戶程序(user-mode daemon)。
現在就如何通過udev動態的創建/dev/目錄下面的文件為例進行說明。
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <asm/system.h> /* cli(), *_flags */
- #include <asm/uaccess.h> /* copy_*_user */
- #include <asm/io.h>
- #include <linux/ioport.h>
- #include <linux/device.h> /* for sys mdev */
- #define LEDS_DEBUG
- #undef PDEBUG /* undef it, just in case */
- #ifdef LEDS_DEBUG
- # ifdef __KERNEL__
- /* This one if debugging is on, and kernel space */
- # define PDEBUG(fmt, args...) printk( KERN_EMERG "leds: " fmt, ## args)
- # else
- /* This one for user space */
- # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
- # endif
- #else
- # define PDEBUG(fmt, args...) /* not debugging: nothing */
- #endif
- #define LEDS_MAJOR 400
- #define LEDON 0
- #define LEDOFF 1
- #define BUZON 2
- #define BUZOFF 3
- #define GPBCON 0x56000010
- struct leds_dev {
- unsigned char value; /* When LED lighted, its value bit is 1, otherwise 0 */
- struct cdev cdev;
- };
- static struct class *leds_class;
- static struct leds_dev *leds_devp;
- static int leds_major = LEDS_MAJOR;
- static void *virtaddr;
- static int leds_open(struct inode *inode, struct file *filp)
- {
- printk("file_operations:open\n");
- return 0;
- }
- static int leds_release(struct inode *inode, struct file *filp)
- {
- printk("file_operations:open\n");
- return 0;
- }
- static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
- {
- printk("file_operations:read\n");
- return 0;
- }
- static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
- {
- printk("file_operations:write\n");
- return 0;
- }
- static int leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
- {
- printk("file_operations:ioctl\n");
- return 0;
- }
- static struct file_operations leds_fops =
- {
- .owner = THIS_MODULE,
- .read = leds_read,
- .ioctl = leds_ioctl,
- .open = leds_open,
- .release = leds_release,
- .write = led_write
- };
- static void leds_setup_cdev(struct leds_dev *dev, int index)
- {
- int err, devno = MKDEV(leds_major, index);
- cdev_init(&dev->cdev, &leds_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &leds_fops;
- err = cdev_add(&dev->cdev, devno, 1);
- if (err)
- {
- printk(KERN_NOTICE "Error adding LEDS\n");
- }
- }
- static int leds_init(void)
- {
- int result;
- dev_t devno = MKDEV(leds_major, 0);
- PDEBUG("enter led_init\n");
- if (leds_major)
- {
- result = register_chrdev_region(devno, 1, "leds");
- }
- else
- {
- result = alloc_chrdev_region(&devno, 0, 10, "leds");
- leds_major = MAJOR(devno);
- }
- if (result < 0)
- {
- return result;
- }
- leds_devp = kmalloc(sizeof(struct leds_dev), GFP_KERNEL);
- memset(leds_devp, 0, sizeof(struct leds_dev));
- leds_setup_cdev(leds_devp, 0);
- leds_class = class_create(THIS_MODULE, "suiyuan_leds_class");
- if (IS_ERR(leds_class))
- {
- printk(KERN_WARNING "failed in creating class\n");
- }
- // class_device_create(leds_class, NULL, devno, NULL, "suiyuan_leds");//這個函數在較早的額內核可以使用
- device_create(leds_class, NULL, devno, NULL, "suiyuan_leds"); //高版本內核可以使用我的是2.6.32.2-FriendlyARM
- printk("leds initialized\n");
- return 0;
- }
- void suiyuan_leds_cleanup(void)
- {
- cdev_del(&leds_devp->cdev);
- kfree(leds_devp);
- unregister_chrdev_region(MKDEV(leds_major, 0), 1);
- //class_device_destroy(leds_class, MKDEV(leds_major, 0));
- device_destroy(leds_class, MKDEV(leds_major, 0));
- class_destroy(leds_class);
- printk("leds driver unloaded\n");
- }
- module_init(leds_init);
- module_exit(suiyuan_leds_cleanup);
- MODULE_AUTHOR("suiyuan");
- MODULE_LICENSE("Dual BSD/GPL");
上面的代碼建立了一個簡單字符的設備,但是在最后使用函數class_create()和device_create()用來分別在/sys和/dev目錄下面創建相應的文件夾及文件。
首先在:在/sys目錄中
[@mini2440 /sys/dev/char]#ls
10:130 116:16 1:11 400:0 //是自己創建的設備文件
[@mini2440 /sys/class]#ls
bdi input net sound
block leds rtc suiyuan_leds_class 是上面的函數class_create()所創建
class_suiyuan mem scsi_device tty
firmware misc scsi_disk vc
graphics mmc_host scsi_generic video4linux
i2c-dev mtd scsi_host vtconsole
在./dev目錄下面:
crw-rw---- 1 0 0 400, 0 Feb 16 20:11 suiyuan_leds 是device_create()函數所創建的設備文件有對應的主從設備號
在執行 cat
[@mini2440 /dev]#cat suiyuan_leds
file_operations:open
file_operations:read
file_operations:open
表示調用了file_operations中的驅動函數,open()和read().
現在在說說 udev.conf文件,現在創建/etc/udev.conf文件在其文件中添加
如下類容:suiyuan_leds 0:5 0666 =MyTestLed #MyTestLed為設備suiyuan_leds的別名。在加載文件系統的時候,將執行udev.conf中的內容。
之后重現加載rootfs 。此時的/dev/如下:
[@mini2440 /dev]#ls -al
total 4
drwxr-xr-x 3 0 0 0 Feb 17 08:34 .
drwxr-xr-x 16 0 0 4096 Feb 13 11:08 ..
crw-rw-rw- 1 0 5 400, 0 Feb 17 08:34 MyTestLed
同時也可以參考mini2440上面提供的udev.conf 文件中的內容。
2:misc設備
下面是一個misc設備的主要組成,結構相對明確。
- #include <linux/miscdevice.h>
- #include <linux/delay.h>
- #include <asm/irq.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/mm.h>
- #include <linux/fs.h>
- #include <linux/types.h>
- #include <linux/delay.h>
- #include <linux/moduleparam.h>
- #include <linux/slab.h>
- #include <linux/errno.h>
- #include <linux/ioctl.h>
- #include <linux/cdev.h>
- #include <linux/string.h>
- #include <linux/list.h>
- #include <linux/pci.h>
- #include <asm/uaccess.h>
- #include <asm/atomic.h>
- #include <asm/unistd.h>
- static int leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
- {
- printk ("misc leds_ioctl\n");
- return 0;
- }
- static int leds_open(struct inode *inode, struct file *filp)
- {
- printk ("misc in leds_open\n");
- return 0;
- }
- static int leds_release(struct inode *inode, struct file *filp)
- {
- printk ("misc leds_release\n");
- return 0;
- }
- static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
- {
- printk ("misc leds_read\n");
- return 1;
- }
- static struct file_operations leds_fops =
- {
- .owner = THIS_MODULE,
- .read = leds_read,
- .ioctl = leds_ioctl,
- .open = leds_open,
- .release = leds_release
- };
- static struct miscdevice misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "misc_leds", //此名稱將顯示在/dev目錄下面
- .fops = &leds_fops,
- };
- static int __init dev_init(void)
- {
- int ret = misc_register(&misc);
- printk ("misc leds initialized\n");
- return ret;
- }
- static void __exit dev_exit(void)
- {
- misc_deregister(&misc);
- printk("misc leds unloaded\n");
- }
- module_init(dev_init);
- module_exit(dev_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("suiyuan");
將上面的代碼保存為suiyuanMisc.c,並編譯進內核。
[root@centos /opt/FriendlyARM/linux-2.6.32.2]$make
CHK include/linux/version.h
make[1]: `include/asm-arm/mach-types.h' is up to date.
CHK include/linux/utsrelease.h
SYMLINK include/asm -> include/asm-arm
CALL scripts/checksyscalls.sh
CHK include/linux/compile.h
CC drivers/suiyuan/suiyuanMisc.o
LD drivers/suiyuan/built-in.o
LD drivers/built-in.o
LD vmlinux.o
MODPOST vmlinux.o
WARNING: vmlinux: 'ledoff' exported twice. Previous export was in vmlinux
WARNING: vmlinux: 'ledon' exported twice. Previous export was in vmlinux
GEN .version
CHK include/linux/compile.h
UPD include/linux/compile.h
CC init/version.o
LD init/built-in.o
LD .tmp_vmlinux1
KSYM .tmp_kallsyms1.S
AS .tmp_kallsyms1.o
LD .tmp_vmlinux2
KSYM .tmp_kallsyms2.S
AS .tmp_kallsyms2.o
LD vmlinux
SYSMAP System.map
SYSMAP .tmp_System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gz
AS arch/arm/boot/compressed/piggy.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
Building modules, stage 2.
MODPOST 17 modules
WARNING: vmlinux: 'ledoff' exported twice. Previous export was in vmlinux
WARNING: vmlinux: 'ledon' exported twice. Previous export was in vmlinux
將zImage轉化為uImage,並通過nfs下載到內容,並啟動。可以在啟動的時候看到:misc leds initialized 信息。
之后可以在mini2440的/dev/目錄下面看到。
有如下設備:crw-rw---- 1 0 0 10, 56 Feb 18 14:44 misc_leds_suiyuan 主設備號為:10,次設備號為:56
同時可以看看文件:/proc/misc,其文件中記載了系統中所加載misc設備。
53 network_throughput
54 network_latency
55 cpu_dma_latency
56 misc_leds_suiyuan
57 leds_03
130 watchdog
58 camera
59 adc
60 pwm
61 buttons
62 leds
63 backlight
misc總結如下:
misc字符設備,該類設備使用同一個主設備號10,misc字符設備使用的數據結構
struct miscdevice {
int minor;
const char *name;
struct file_operations *fops;
struct list_head list;
struct device *dev;
struct class_device *class;
char devfs_name[64];
};
雜項設備(misc device)
在 Linux 內核的include\linux\miscdevice.h文件,要把自己定義的misc device從設備定義在這里。其實是因為這些字符設備不符合預先確定的字符設備范疇,所有這些設備采用主設備號10 ,一起歸於misc device,其實misc_register就是用主設備號10調用register_chrdev()的。也就是說,misc設備其實也就是特殊的字符設備。
misc_device是特殊的字符設備。注冊驅動程序時采用misc_register函數注冊,此函數中會自動創建設備節點,即設備文件。無需mknod指令創建設備文件。因為misc_register()會調用class_device_create()或者device_create()。
因此使用misc創建字符設備最簡單方便。