字符设备就是:一个一个字节来进行访问的,不能对字符设备进行随机读写。简单字符设备创建实例如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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()没有做任何东西。
现在将创建一个基本的字符设备过程总结如下:
A:创建设备号。MKDEV(major_no,0),其值为一个整数。因为linux中使用设备号来关联相应的设备和设备对于的驱动程序。
B:注册设备号。register_chrdev_region(devno,1,"led1_dev")或者alloc_chrdev_region(&devno,0,1,"led1_dev");//动态申请设备号
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()函数。应用程序和底层驱动就此联系了起来。
udev文件系统是针对2.6内核,提供一个基于用户空间的动态设备节点管理和命名的使用程序,udev 以守护进程的形式运行,通过侦听内核发出来的 uevent 来动态的管理/dev
目录下的设备文件。:udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev动态的根据根文件系统目录下面的/sys文件主动的创建设备文件,故在编写好设备驱动的时候,不需要再手动通过命令mknod来创建设备文件。udev涉及到/sys目录下面的文件,而/sys涉及到linux设备驱动模式。以后自己在总结这方面的东西。udev是一个用户程序(user-mode daemon)。
现在就如何通过udev动态的创建/dev/目录下面的文件为例进行说明。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* cli(), *_flags */
#include /* copy_*_user */
#include
#include
#include /* 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 文件中的内容。
下面是一个misc设备的主要组成,结构相对明确。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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设备。
[@mini2440 /proc]#cat 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字符设备,该类设备使用同一个主设备号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创建字符设备最简单方便。