quik能在quik电脑版如何使用剪辑吗

您现在的位置:&&>>&&>>&&>>&&>>&正文
Linux&字符设备驱动程序实现
&&& 一、主设备号和此设备号&&& &&& 主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。&&& &&& 内核用dev_t类型(&/types.h&)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。&&& &&& 在实际使用中,是通过&/kdev_t.h&中定义的宏来转换格式。&&& &&& (dev_t)--&主设备号、次设备号&&& &&& MAJOR(dev_t dev)&&& &&& MINOR(dev_t dev)&&& &&& 主设备号、次设备号--&(dev_t)&&& &&& MKDEV(int major,int minor)&&& &&& 建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在&linux/fs.h&中声明:&&& &&& int register_chrdev_region(dev_t first, unsigned int count,&&& &&& char *name); //指定设备编号&&& &&& int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,&&& &&& unsigned int count, char *name); //动态生成设备编号&&& &&& void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号&&& &&& 分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。&&& &&& 以下是在scull.c中用来获取主设备好的代码:&&& &&& 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);&&& &&&&&& &&& }&&& &&& 在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。&&& &&& 看到这里,就可以理解为什么mdev和udev可以动态、自动地生成当前系统需要的设备文件。udev就是通过读取sysfs下的信息来识别硬件设备的。&&& &&& 二、一些重要的数据结构&&& &&& 大部分基本的驱动程序操作涉及及到三个重要的内核数据结构,分别是file_operations、file和inode,它们的定义都在&linux/fs.h&.&&& &&& file_operations&&& &&& 是一个函数指针的集合,标记式初始化允许结构成员重新排序; 在某种情况下, 真实的性能提高已经实现, 通过安放经常使用的成员的指针在相同硬件高速行中。&&& &&& struct module *owner&&& &&& 第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针。 这个成员用来在它的操作还在被使用时阻止模块被卸载。 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 &linux/module.h& 中定义的宏。&&& &&& loff_t (*llseek) (struct file *, loff_t, int);&&& &&& llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值。 loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽。 错误由一个负返回值指示。 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述)。&&& &&& ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);&&& &&& 用来从设备中获取数据。 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败。 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型)。&&& &&& ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);&&& &&& 初始化一个异步读 -- 可能在函数返回前不结束的读操作。 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地)。&&& &&& ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);&&& &&& 发送数据给设备。 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序。 如果非负, 返回值代表成功写的字节数。&&& &&& ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);&&& &&& 初始化设备上的一个异步写。&&& &&& int (*readdir) (struct file *, void *, filldir_t);&&& &&& 对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用。&&& &&& unsigned int (*poll) (struct file *, struct poll_table_struct *);&&& &&& poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞。 poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能。 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写。&&& &&& int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);&&& &&& ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写)。 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表。 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误。&&& &&& int (*mmap) (struct file *, struct vm_area_struct *);&&& &&& mmap 用来请求将设备内存映射到进程的地址空间。 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.&&& &&& int (*open) (struct inode *, struct file *);&&& &&& 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法。 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知。&&& &&& int (*flush) (struct file *);&&& &&& flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作。 这个必须不要和用户查询请求的 fsync 操作混淆了。 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上。 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求。&&& &&& int (*release) (struct inode *, struct file *);&&& &&& 在文件结构被释放时引用这个操作。 如同 open, release 可以为 NULL.&&& &&& int (*fsync) (struct file *, struct dentry *, int);&&& &&& 这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据。 如果这个指针是 NULL, 系统调用返回 -EINVAL.&&& &&& int (*aio_fsync)(struct kiocb *, int);&&& &&& 这是 fsync 方法的异步版本。&&& &&& int (*fasync) (int, struct file *, int);&&& &&& 这个操作用来通知设备它的 FASYNC 标志的改变。 异步通知是一个高级的主题, 在第 6 章中描述。 这个成员可以是NULL 如果驱动不支持异步通知。&&& &&& int (*lock) (struct file *, int, struct file_lock *);&&& &&& lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它。&&& &&& ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);&&& &&& ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);&&& &&& 这些方法实现发散/汇聚读和写操作。 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝。 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 )。&&& &&& ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);&&& &&& 这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个。 例如, 它被一个需要发送文件内容到一个网络连接的 web 使用。 设备驱动常常使 sendfile 为 NULL.&&& &&& ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);&&& &&& sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件。 设备驱动实际上不实现 sendpage.&&& &&& unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);&&& &&& 这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中。 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求。 大部分驱动可以置这个方法为 NULL.[10 ]&&& &&& int (*check_flags)(int)&&& &&& 这个方法允许模块检查传递给 fnctl(F_SETFL…) 调用的标志。&&& &&& int (*dir_notify)(struct file *, unsigned long);&&& &&& 这个方法在应用程序使用 fcntl 来请求目录改变通知时调用。 只对文件系统有用; 驱动不需要实现 dir_notify.&&& &&& struct file_operations scull_fops = {&&& &&& .owner = THIS_MODULE,&&& &&& .llseek = scull_llseek,&&& &&& .read = scull_read,&&& &&& .write = scull_write,&&& &&& .ioctl = scull_ioctl,&&& &&& .open = scull_open,&&& &&& .release = scull_release,&&& &&& };&&& &&& 文件结构&&& &&& struct file, 定义于 &linux/fs.h&, 是设备驱动中第二个最重要的数据结构。 注意 file 与用户空间程序的 FILE 指针没有任何关系。 一个 FILE 定义在 C 库中, 从不出现在内核代码中。 一个 struct file, 另一方面, 是一个内核结构, 从不出现在用户程序中。&&& &&& 在内核源码中, struct file 的指针常常称为 file 或者 filp("file pointer")。 我们将一直称这个指针为 filp 以避免和结构自身混淆。 因此, file 指的是结构, 而 filp 是结构指针。&&& &&& struct file 的最重要成员在这展示。 如同在前一节, 第一次阅读可以跳过这个列表。 但是, 在本章后面, 当我们面对一些真实 C 代码时, 我们将更详细讨论这些成员。&&& &&& mode_t f_&&& &&& 文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可, 但是你不需要检查读写许可, 因为内核在调用你的方法之前检查。 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况。&&& &&& loff_t f_&&& &&& 当前读写位置。 loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long )。 驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp-&f_pos. 这个规则的一个例外是在 llseek 方法中, 它的目的就是改变文件位置。&&& &&& unsigned int f_&&& &&& 这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作( 我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞 I/O ); 其他标志很少使用。 特别地, 应当检查读/写许可, 使用 f_mode 而不是 f_flags. 所有的标志在头文件 &linux/fcntl.h& 中定义。&&& &&& struct file_operations *f_&&& &&& 和文件关联的操作。 内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时。 filp-&f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用。 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的 open 代码根据打开的次编号来替代 filp-&f_op 中的操作。 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销。 替换文件操作的能力是面向对象编程的"方法重载"的内核对等体。&&& &&& void *private_&&& &&& open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前。 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存。 private_data 是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它。&&& &&& struct dentry *f_&&& &&& 关联到文件的目录入口( dentry )结构。 设备驱动编写者正常地不需要关心 dentry 结构, 除了作为 filp-&f_dentry-&d_inode 存取 inode 结构。&&& &&& 真实结构有多几个成员, 但是它们对设备驱动没有用处。 我们可以地忽略这些成员, 因为驱动从不创建文件结构; 它们真实存取别处创建的结构。&&& &&& inode 结构&&& &&& inode 结构由内核在内部用来表示文件。 因此, 它和代表打开文件描述符的文件结构是不同的。 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构。&&& &&& inode 结构包含大量关于文件的信息。 作为一个通用的规则, 这个结构只有 2 个成员对于编写驱动代码有用:&&& &&& dev_t i_&&& &&& 对于代表设备文件的节点, 这个成员包含实际的设备编号。&&& &&& struct cdev *i_&&& &&& struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时。&&& &&& i_rdev 类型在 2.5 开发系列中改变了, 破坏了大量的驱动。 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏, 可用来从一个 inode 中获取主次编号:&&& &&& unsigned int iminor(struct inode *inode);&&& &&& unsigned int imajor(struct inode *inode);&&& &&& 为了不要被下一次改动抓住, 应当使用这些宏代替直接操作 i_rdev.&&& &&& 三、字符设备的注册&&& &&& 内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev.代码应包含&linux/cdev.h&,它定义了struct cdev以及与其相关的一些辅助函数。&&& &&& 注册一个独立的cdev设备的基本过程如下:&&& &&& 1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)&&& &&& struct cdev *my_cdev = cdev_alloc();&&& &&& my_cdev-&ops=&my_&&& &&& 2、初始化struct cdev&&& &&& void cdev_init(struct cdev *cdev, const struct file_operations *fops)&&& &&& 3、初始化cdev.owner&&& &&& cdev.owner = THIS_MODULE;&&& &&& 4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)&&& &&& int cdev_add(struct cdev *p, dev_t dev, unsigned count)&&& &&& 这里, dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目。 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形。 例如, 设想 SCSI 磁带驱动, 它允许用户空间来选择操作模式(例如密度), 通过安排多个次编号给每一个物理设备。&&& &&& 在使用 cdev_add 是有几个重要事情要记住。 第一个是这个调用可能失败。 如果它返回一个负的错误码, 你的设备没有增加到系统中。 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一返回, 你的设备就是"活的"并且内核可以调用它的操作。 除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add.[1]&&&&
【责编:ivy】
?&[]?&[]?&[]?&[]?&[]?&[]?&[]?&[]?&[]?&[]
相关产品和培训
 友情推荐链接
 专题推荐
 ? ? ? ? ? ? ? ? ? ?
 今日更新
?&?&?&?&?&?&?&?&?&?&
 认证培训
 频道精选
 Windows频道导航
                      Linux实现字符设备驱动的基础步骤 - 推酷
Linux实现字符设备驱动的基础步骤
Linux应用层想要操作kernel层的API,比如想操作相关GPIO或寄存器,可以通过写一个字符设备驱动来实现。
1、先在rootfs中的 /dev/ 下生成一个字符设备。注意主设备号 和 从设备号。可用如下shell脚本生成:
if [ ! -e audioIN ];then
sudo mknod audioIN c 240 0
生成的设备为 /dev/audioIN ,主设备号240,从设备号0。
2、写audioINdriver.ko ,audioINdriver.c 基本代码框架如下:代码中定义了设备名audioIN,设备号240, 0 ,与之前创建的设备一致。
/**************************************************************************\
* audioINdriver.c
* kang_liu &&
\**************************************************************************/
#include &asm/uaccess.h&
#include &asm/errno.h&
#include &linux/types.h&
#include &linux/fs.h&
#include &linux/init.h&
#include &linux/module.h&
#include &linux/cdev.h&
#include &mach/gpio.h&
//#include &mach/at91_rstc.h& /* debug */
//#include &mach/at91_pmc.h&
//#include &mach/at91_rstc.h&
//#include &mach/at91_shdwc.h&
#include &mach/irqs.h&
//#include &generic.h&
//#include &clock.h&
#include &mach/w55fa92_reg.h&
#include &asm/io.h&
#define DEV_MAJOR 240
#define DEV_MINOR 0
#define NUM_MINORS 1
#define DEVICE_NAME &audioIN&
#define ERR(fmt, args...) printk(KERN_ALERT __FILE__ &: & fmt, ##args)
#define MSG(fmt, args...) printk(KERN_INFO __FILE__ &: & fmt, ##args)
#define DBG(fmt, args...) printk(KERN_DEBUG __FILE__ &: & fmt, ##args)
static ssize_t user_gpio_read(struct file *fp, char __user *buff,
size_t count, loff_t *offp)
char str[32] = {0};
char out[32] = {0};
// printk(&lk~~~~~~~read buff = %s\n&,buff);
err = copy_from_user(str, buff, count);
// printk(&lk~~~~~~~read str = %s\n&,str);
return -EFAULT;
sprintf(out,&return values&);
memset(buff, 0, count);
err = copy_to_user(buff, out, sizeof(out));
return -EFAULT;
static ssize_t user_gpio_write(struct file *fp, const char __user *buff,
size_t count, loff_t *offp)
char tmp[32];
// printk(&lk~~~~~~~write buff = %s\n&,buff);
err = copy_from_user(tmp, buff, count);
// printk(&lk~~~~~~~write tmp = %s\n&,tmp);
return -EFAULT;
if('1' == tmp[0])
printk(&line in\n&);
else if('0' == tmp[0])
printk(&mic in\n&);
static ssize_t user_gpio_open(struct inode *inode,struct file *fp)
printk(&open gpio devices\n&);
static struct file_operations user_gpio_file_ops =
.owner = THIS_MODULE,
.write = user_gpio_write,
.read = user_gpio_read,
.open = user_gpio_open,
static struct cdev *
static void __exit user_audioIN_exit(void)
printk(&exit audioIN\n&);
dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR);
unregister_chrdev_region(devno, NUM_MINORS);
cdev_del(dev);
static int __init user_audioIN_init(void)
printk(&init audioIN\n&);
int err = 0;
dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR);
err = register_chrdev_region(devno, NUM_MINORS, DEVICE_NAME);
goto fail_
dev = cdev_alloc();
dev-&ops = &user_gpio_file_
dev-&owner = THIS_MODULE;
err = cdev_add(dev, devno, NUM_MINORS);
goto fail_
fail_cdev:
fail_devno:
unregister_chrdev_region(devno, NUM_MINORS);
fail_gpio:
module_init(user_audioIN_init);
module_exit(user_audioIN_exit);
MODULE_LICENSE(&GPL&);
MODULE_AUTHOR(&kang_liu &&&);
MODULE_DESCRIPTION(&Access GSEIO from userspace.&);
这里就可以调用kernel层的一些API进行底层的操作。
Makefile:生成audioINdriver.ko
# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y
BUILD_TOOLS_PRE = arm-linux-
CC=$(BUILD_TOOLS_PRE)gcc
LD=$(BUILD_TOOLS_PRE)ld
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSCULL_DEBUG # &-O& is needed to expand inlines
DEBFLAGS = -O2
KERNEL_DIR = ../../../linux-2.6.35.4
EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_CFLAGS += -I$(LDDINC)
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/mach-w55fa92/include
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/include
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/include/linux
ifneq ($(KERNELRELEASE),)
# call from kernel build system
audioIN-objs := audioINdriver.o
obj-m := audioINdriver.o
KERNELDIR ?= $(KERNEL_DIR)
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
:= $(shell pwd)
$(MAKE) ARCH=arm CROSS_COMPILE=$(BUILD_TOOLS_PRE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
depend .depend dep:
$(CC) $(CFLAGS) -M *.c & .depend
ifeq (.depend,$(wildcard .depend))
include .depend
3. 生成好 .ko 以后,就可以在ARM板上,加载驱动。
insmod audioINdriver.ko
4、加载驱动成功后,就可以在应用层直接操作设备 /dev/audioIN,来实现相关功能,将一些参数传到驱动层,执行相关kernel层的代码。
应用层测试程序如下:
#include &stdio.h&
#include &stdlib.h&
#include &string.h&
#include &unistd.h&
#include &errno.h&
#include &fcntl.h&
#include &sys/types.h&
#include &sys/stat.h&
#include &sys/syscall.h&
#define BUF_LEN 32
int s_audioInFd = 0;
int InitAudioInDevice()
s_audioInFd = open(&/dev/audioIN&,O_RDWR);
if (s_audioInFd & 0)
printf(&Can't open the GSE IO device\n&);
void UninitAudioInDevice()
if (s_audioInFd & 0)
close(s_audioInFd);
int getAudioIn()
char buffer[BUF_LEN] = {0};
if (s_audioInFd & 0)
memcpy(&buffer[0], &lk_test&, 7);
printf(&get buffer = %s\n&, buffer);
int len = read(s_audioInFd, buffer, 7);
printf(&get buffer = %s, len = %d\n&, buffer, len);
return -1;
int setAudioIn(int micLine)
char buffer[BUF_LEN] = {0};
if (s_audioInFd & 0)
sprintf(buffer, &%d&, micLine);
int len = write(s_audioInFd, buffer, sizeof(buffer));
if (len & 0)
其中的read 和 write函数,可从驱动中获取一些返回值,也可将字符串传到驱动中。
驱动的入口为:
module_init(user_audioIN_init);
module_exit(user_audioIN_exit);
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致Linux设备驱动:如何写字符设备驱动(1)
Linux设备驱动:如何写字符设备驱动(1)
16:25:48来源:
[导读] 如何写Linux字符设备驱动?首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作。1、主设备号和次设备号对于字符设备的访问是通过文件系统中的设备名称进行的。他们通常位于 dev目录下。如下:其中b代表块设备,c
如何写Linux字符设备驱动?首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作。
1、主设备号和次设备号
对于字符设备的访问是通过文件系统中的设备名称进行的。他们通常位于/dev目录下。如下:
其中b代表块设备,c代表字符设备。对于普通文件来说,ls -l会列出文件的长度,而对于设备文件来说,上面的7,5,4等代表的是对应设备的主设备号,而后面的0,1,2,10等则是对应设备的次设备号。那么主设备号和次设备号分别代表什么意义呢?一般情况下,可以这样理解,主设备号标识设备对应的驱动程序,也就是说1个主设备号对应一个驱动程序。当然,现在也有多个驱动程序共享主设备号的情况。而次设备号有内核使用,用于确定/dev下的设备文件对应的具体设备。举一个例子,虚拟控制台和串口终端有驱动程序4管理,而不同的终端分别有不同的次设备号。
1.1、设备编号的表达
在内核中,dev_t用来保存设备编号,包括主设备号和次设备号。在2.6的内核版本种,dev_t是一个32位的数,其中12位用来表示主设备号,其余20位用来标识次设备号。
通过dev_t获取主设备号和次设备号使用下面的宏:
MAJOR(dev_t dev);
MINOR(dev_t dev);
相反,通过主设备号和次设备号转换为dev_t类型使用:
MKDEV(int major, int minor);
1.2、分配和释放设备编号
在构建一个字符设备之前,驱动程序首先要获得一个或者多个设备编号,这类似一个营业执照,有了营业执照才在内核中正常工作营业。完成此工作的函数是:
int register_chrdev_region(dev_t first, unsigned int count, const char *name);
first是要分配的设备编号范围的起始值。count是连续设备的编号的个数。name是和该设备编号范围关联的设备名称,他将出现在/proc/devices和sysfs中。此函数成功返回0,失败返回负的错误码。此函数是在已知主设备号的情况下使用,在未知主设备号的情况下,我们使用下面的函数:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char* name);
dev用于输出申请到的设备编号,firstminor要使用的第一个此设备编号。
在不使用时需要释放这些设备编号,已提供其他设备程序使用:
void unregister_chrdev_region(dev_t dev, unsigned int count);
此函数多在模块的清除函数中调用。
分配到设备编号之后,我们只是拿到了营业执照,虽说现在已经准备的差不多了,但是我们只是从内核中申请到了设备号,应用程序还是不能对此设备作任何事情,我们需要一个简单的函数来把设备编号和此设备能实现的功能连接起来,这样我们的模块才能提供具体的功能.这个操作很简单,稍后就会提到,下一篇我们再来介绍几个重要的数据结构。
小班面授,全程项目实战,理论实战1:4分配
课程涵盖:安全运维、虚拟化工程、数据库、高级系统工程
课程涵盖:JAVA 前端 大数据 .net 数据库
理论课实战课1:1分配
&&跟着大牛的步伐,独家...
年味的记忆嘉华教育集团第二届手机摄影大赛
工资低、福利差、想转行?
刚毕业、没规划、很迷茫?
别让时光蹉跎了梦想,学技术做更好的自己!...
互联网+时代高薪职业——UI设计师
会ps,你顶多是个p图的
学UI,做高薪全能设计师
北大青鸟前端开发工程师
支持你成为一名高薪前端开发工程师
IT行业就业前景如何你造吗?做互联网营销精英,你能行吗?
先入行的都月薪过万了,你还在观望什...
挑战年薪20万,你准备好了吗?
深圳嘉华学校Java开发课程
助你一跃成技术大咖
提升办公软件技能
笑傲职场做白领
深圳嘉华办公软件培训助你华丽转身
2014年双11成交额571亿背后的秘密?
你知道未来三年电子商务人才的缺口数是多少么?
你是否总...
相关文章推荐
自从互联网进入中国以来便一发不可收拾,经过多年的迅速发展,如今已成为了世界性的朝阳行业,...
深圳IT职业技能培训,深圳北大青鸟职业技能培训北大青鸟IT职业教育创造性地将特许经营模式引入到I...
女孩子学电脑学哪个方面的比较好?深圳南山北大青鸟的职业规划老师指出,一个好的专业能决定你有...
深圳北大青鸟嘉华老师指出,300多分是一个非常尴尬的分数,考生只能读一个普通的大专。随着高...
沙井附近工厂很多,在这里打工的年轻普工们在工厂打工一两年后都期望自己能找到一份更好的工作。...
  随着IT用人市场的不断升温,IT人才需求火爆,让越来越多的培训教育机构介入了IT领域。对求学...
又开到一年结业季,一大批结业生即将走出校区,踏入社会。而此刻对他们来说更主要的是怎么才能找...
众所周知,每年春节过后都是跳槽的&黄金旺季&。据相关调查报告显示, 90后&小鲜肉&们跳槽热...
易学性是JAVA技术的特点之一,也是目前使用最为广泛的网络编程语言之一。他容易学而且很好用,如...
JAVA软件工程师就业前景怎么样?JAVA软件工程师好就业吗?JAVA软件工程师就业发展前景真的好吗...

我要回帖

更多关于 gopro quik电脑版下载 的文章

 

随机推荐