




已阅读5页,还剩18页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第一章 驱动程序基本框架星期二, 06/08/2010 - 00:21 william 前言不管是Windows还是Linux,驱动程序都扮演着重要的角色。应用程序只能通过驱动程序才能同硬件设备或系统内核通讯。Linux内核对不同的系统 定义了标准的接口(API),应用程序就是通过这些标准的接口来操作内核和硬件。驱动可以被编译的内核中(build-in),也可以做为内核模块 (Module)存在于内核的外面,需要的时候动态插入到内核中运行。就像你学习操作系统概念时所了解的那样,Linux内核也分为几个大的部分:进程管理、内存管理、文件系统、设备控制、网络系统等,参考图1-1。图1-1 Linux系统(来源:OReilly Media, LDD3)这里就不对Linux系统内核的各个部分做过多的介绍了,在后面的学习中你就会逐渐地对这些概念有个更深入的了解 。其实Linux内核的精髓远不止这些,对于一个Linux内核的爱好者或开发者来说,最好详细的浏览内核源代码,订阅Linux内核相关的邮件列表,或 是登陆Linux开发社区。更多的信息,请登陆Linux内核官方网站:一个简单的驱动下面我们来编写第一个驱动程序,它很简单,在运行时会输出Hello World消息。/ hello.c#include #include #include static int _init hello_init(void) printk(KERN_ALERT Hello World!n); return 0;static void _exit hello_exit(void) printk(KERN_ALERT Goodbye World!n);module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(GPL);这就是一个简单的驱动程序,它什么也没做,仅仅是输出一些信息,不过对于我们来说这已经足够了。保存这个程序,命名为hello.c。在写一个 Makefile文件用来编译它,Makefile和hello.c文件保存在同一个目录下。#Makefileifneq ($(KERNELRELEASE),)MODULE_NAME = helloworld$(MODULE_NAME)-objs := hello.oobj-m := $(MODULE_NAME).oelseKERNEL_DIR = /lib/modules/uname -r/buildMODULEDIR := $(shell pwd).PHONY: modulesdefault: modulesmodules: make -C $(KERNEL_DIR) M=$(MODULEDIR) modulesclean distclean: rm -f *.o *.mod.c .*.*.cmd *.ko rm -rf .tmp_versionsendif编译并运行这个模块:/需要root权限来运行makeinsmod helloworld.kormmod helloworld.ko尽管我们对它的一些细节还不够了解,它确实神奇的工作了,这个Hello World信息输出到了屏幕终端上(不是VT),或者系统的Kenrel log里(/var/log/messages),你可以通过运行dmesg来看到这些信息。驱动基础我们通过分析上面的代码来了解一个驱动程序的基本概念。 头文件: 就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:#include #include #include init.h 定义了驱动的初始化和退出相关的函数 kernel.h 定义了经常用到的函数原型及宏定义 module.h 定义了内核模块相关的函数、变量及宏 初始化 任何一个驱动都去需要提供一个初始化函数,当驱动加载到内核中时,这个初始化函数就会被自动执行,初始化的函数原型定义如下:typedef int (*initcall_t)(void);驱动程序是通过module_init宏来声明初始化函数的:static int _init hello_init(void) printk(KERN_ALERT Hello World!n); return 0;module_init(hello_init);_init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,这样当函数初始化完成后这个区域可以被清除掉以节约系统内存。 Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。初始化函数是有返回值的,只有在初始化成功是才返回0,否则返回错误码(errno)。 卸载 如果驱动程序编译成模块(动态加载)模式,那么它需要一个清理函数。当移除一个内核模块时这个函数被调用执行清理工作。清理函数的函数原型定义为:typedef void (*exitcall_t)(void);驱动程序是通过module_exit宏来声明清理函数的:static void _exit hello_exit(void) printk(KERN_ALERT Goodbye World!n);module_exit(hello_exit);同_init类似,如果驱动被编译进内核,则_exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作。显然,_init和_exit 对动态加载的模块是无效的。 版权信息 Linux内核是按照GPL发布的,同样Linux的驱动程序也要提供版权信息,否则当加载到内核中是系统会给出警告信息。Hello World例子中的版权信息是GPL。MODULE_LICENSE(GPL);后续:这里你了解了一个驱动程序的基本框架,所有的驱动都会包含这些内容。这里我们没有对Linux 驱动程序的编译系统做详细的介绍,因为它相对C应用程序的编译有些复杂。Linux2.6内核采用Kbuild系统做编译,下一章你会了解到Kbuild 的详细内容。第二章 Kbuild系统星期二, 06/08/2010 - 21:43 william 序言从Linux内核2.6开始,Linux内核的编译采用Kbuild系统,这同过去的编译系统有很大的不同,尤其对于Linux内核模块的编译。在新的系统下,Linux编译系统会两次扫描Linux的Makefile:首先编译系统会读取Linux内核顶层的Makefile,然后根据读到的内容第二次读取Kbuild的Makefile来编译Linux内核。Linux内核Makefile分类 Kernel Makefile Kernel Makefile位于Linux内核源代码的顶层目录,也叫 Top Makefile。它主要用于指定编译Linux Kernel目标文件(vmlinux)和模块(module)。这编译内核或模块是,这个文件会被首先读取,并根据读到的内容配置编译环境变量。对于内核或驱动开发人员来说, 这个文件几乎不用任何修改。 Kbuild Makefile Kbuild系统使用Kbuild Makefile来编译内核或模块。当Kernel Makefile被解析完成后,Kbuild会读取相关的Kbuild Makefile进行内核或模块的编译。Kbuild Makefile有特定的语法指定哪些编译进内核中、哪些编译为模块、及对应的源文件是什么等。内核及驱动开发人员需要编写这个Kbuild Makefile文件。 ARCH Makefile ARCH Makefile位于ARCH/$(ARCH)/Makefile,是系统对应平台的Makefile。Kernel Top Makefile会包含这个文件来指定平台相关信息。只有平台开发人员会关心这个文件。Kbuild MakefileKbuild Makefile的文件名不一定是Makefile,尽管推荐使用Makefile这个名字。大多的Kbuild文件的名字都是Makefile。为了与其他Makefile文件相区别,你也可以指定Kbuild Makefile的名字为Kbuild。而且如果“Makefile”和“Kbuild”文件同时存在,则Kbuild系统会使用“Kbuild”文件。 目标定义 Kbuild Makefile的一个最主要功能就是指定编译什么,这个功能是通过下面两个对象指定的obj-?和xxx-objs: obj-? obj-?指定编译什么,怎么编译?其中的“?”可能是“y”或“m”,“y”指定把对象编译进内核中,“m”指定把对象编译为模块。语法如下; obj-? = $(target).otarget为编译对象的名字。如果没有指定xxx-objs,这编译这个对象需要的源文件就是$(target).c或$(target).s。如果指定了$(target)-objs,则编译这个对象需要的源文件由$(target)-objs指定,并且不能有$(target).c或$(target).s文件。 xxx-objs xxx-objs指定了编译对象需要的文件,一般只有在源文件是多个时才需要它。只要包含了这两行,Kbuild Makefile就应该可以工作了。 嵌套编译 有时一个对象可能嵌入到另一个对象的目录下,那个如何编译子目录下的对象呢?其实很简单,只要指定obj_?的对象为子目录的名字就可以了:obj-? = $(sub_target)/其中“?”可以是“y”或“m”,$(sub_target)是子目录名字。 编译器选项 尽管在大多数情况下不需要指定编译器选项,有时我们还是需要指定一些编译选项的。 ccflags-y, asflags-y and ldflags-y 这些编译选项用于指定cc、as和ld的编译选项编译外部模块有时候我们需要在内核源代码数的外面编译内核模块,编译的基本命令是: make -C $(KERNEL_DIR) M=pwd modules我们可以把这个命令集成到Makefile里,这样我们就可以只输入“make”命令就可以了。回想上一章的那个Makefile,它把Normal Makefile 和Kbuild Makefile集成到一个文件中了。为了区别Kbuild Makefile 和Normal Makefile,这样我们改写Makefile为如下形式,并且添加Kbuild Makefile - “Kbuild”。#Makefileifneq ($(KERNELRELEASE),)include KbuildelseKERNEL_DIR = /lib/modules/uname -r/buildMODULEDIR := $(shell pwd) .PHONY: modulesdefault: modulesmodules: make -C $(KERNEL_DIR) M=$(MODULEDIR) modulesclean distclean: rm -f *.o *.mod.c .*.*.cmd *.ko rm -rf .tmp_versionsendif# KbuildMODULE_NAME = helloworld$(MODULE_NAME)-objs := hello.oobj-m := $(MODULE_NAME).o一般不需要在Makefile里包含如下代码,这样写完全是为了兼容老版本的Kbuild系统。KERNELRELEASE变量在Kernel Makefile里定义的,因此只有在第二次由Kbuild读取这个Makefile文件时才会解析到Kbuild的内容。ifneq ($(KERNELRELEASE),)include Kbuildelse.endif外部头文件有时需要连接内核源代码外部的系统头文件,但Kbuild系统默认的系统头文件都在内核源代码内部,如何使用外部的头文件呢?这个可以借助于Kbuild系统的特殊规则: EXTRA_CFLAGS EXTRA_CFLAGS可以给Kbuild系统添加外部系统头文件, EXTRA_CFLAGS += $(ext_include_path)一般外部头文件可能位于外部模块源文件的目录内,如何指定呢?这可以借助$(src)或$(obj) $(src)/$(obj) $(src)是一个相对路径,它就是Makefile/Kbuild文件所在的路径。同样$(obj)就是编译目标保存的路径,默认就是源代码所在路径。因此,我们修改Kbuild文件添加 EXTRA_CFLAGS 来包含外部头文件尽管在这个驱动里没有引用外部系统头文件:# KbuildMODULE_NAME = helloworld$(MODULE_NAME)-objs := hello.oEXTRA_CFLAGS := -I$(src)/includeobj-m := $(MODULE_NAME).o后记这里我们详细的介绍了Linux内核的Kbuild系统,相信你已经可以自如的写自己的Kbuild Makefile了。现在开始,我们就可以写带有一些功能的驱动程序了。Linux里的驱动程序可以分为三类,字符设备驱动程序,块设备驱动程序和网络设备驱动程序。在后面的章节里你会详细的了解到这些驱动程序,同时可以了解到Linux内核里用到的一些技术,如进程管理、内存管理、内核同步技术、内核时钟等。第三章 字符设备驱动星期四, 06/10/2010 - 08:59 william 序言Linux下的大部分驱动程序都是字符设备驱动程序,在这一章我们就扩展我们的“Hello World”程序来支持用户应用程序的读写操作。我们也会了解到字符设备是如何注册到系统中的,应用程序是如何访问驱动程序的数据的,及字符驱动程序是如何工作的。设备号通过前面的学习我们知道应用程序是通过设备节点来访问驱动程序及设备的,其根本是通过设备节点的设备号(主设备号及从设备号)来关联驱动程序及设备的,字符设备也不例外(其实字符设备只能这样访问)。这里我们详细讨论Linux内部如何管理设备号的。 设备号类型 Linux内核里用“dev_t”来表示设备号,它是一个32位的无符号数,其高12位用来表示主设备号,低20位用来表示从设备号。它被定义在头文件里。内核里提供了操作“dev_t”的函数,驱动程序中通过这些函数(其实是宏,定义在文件中)来操作设备号。#define MINORBITS 20#define MINORMASK (1U MINORBITS)#define MINOR(dev) (unsigned int) (dev) & MINORMASK)#define MKDEV(ma,mi) (ma) MINORBITS) | (mi)MAJOR(dev)用于获取主设备号,MINOR(dev)用于获取从设备号,而MKDEV(ma,mi)用于通过主设备号和从设备号构造dev_t数据。另一点需要说明的是,dev_t数据类型支持212个主设备号,每个主设备号(通常是一个设备驱动)可以支持220个设备,目前来说这已经足够大了,但谁又能说将来还能满足要求呢?一个良好的编程习惯是不要依赖dev_t这个数据类型,切记必须使用内核提供的操作设备号的函数。 字符设备号注册 内核提供了字符设备号管理的函数接口,作为一个良好的编程习惯,字符设备驱动程序应该通过这些函数向系统注册或注销字符设备号。int register_chrdev_region(dev_t from, unsigned count, const char *name)int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)void unregister_chrdev_region(dev_t from, unsigned count)register_chrdev_region用于向内核注册已知可用的设备号(次设备号通常是0)范围。由于历史的原因一些设备的设备号是固定的,你可以在内核源代码树的Documentation/devices.txt文件中找到这些静态分配的设备号。alloc_chrdev_region用于动态分配的设备号并注册到内核中,分配的设备号通过dev参数返回。作为一个良好的内核开发习惯,我们推荐你使用动态分配的方式来生成设备号。unregister_chrdev_region用于注销一个不用的设备号区域,通常这个函数在驱动程序卸载时被调用。字符设备Linux2.6内核使用“struct cdev”来记录字符设备的信息,内核也提供了相关的函数来操作“struct cdev”对象,他们定义在头文件中。可见字符设备及其操作函数接口定义的很简单。struct cdev struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count;void cdev_init(struct cdev *, const struct file_operations *);struct cdev *cdev_alloc(void);void cdev_put(struct cdev *p);int cdev_add(struct cdev *, dev_t, unsigned);void cdev_del(struct cdev *);对于Linux 2.6内核来说,struct cdev是内核字符设备的基础结构,用来表示一个字符设备,包含了字符设备需要的全部信息。 kobj:struct kobject对象数据,用来描述设备的引用计数,是Linux设备模型的基础结构。我们在后面的“Linux设备模型”在做详细的介绍。 owner:struct module对象数据,描述了模块的属主,指向拥有这个结构的模块的指针,显然它只有对编译为模块方式的驱动才由意义。一般赋值位“THIS_MODULE”。 ops:struct file_operations对象数据,描述了字符设备的操作函数指针。对于设备驱动来说,这是一个很重要的数据成员,几乎所有的驱动都要用到这个对象,我们会在下面做详细介绍。 dev:dev_t对象数据,描述了字符设备的设备号。 内核提供了操作字符设备对象“struct cdev”的函数,我们只能通过这些函数来操作字符设备,例如:初始化、注册、添加、移除字符设备。 cdev_alloc:用于动态分配一个新的字符设备 cdev 对象,并对其进行初始化。采用cdev_alloc分配的cdev对象需要显示的初始化owner和ops对象。 / 参考drivers/scsi/st.c:st_probe 函数struct cdev *cdev = NULL;cdev = cdev_alloc();/ Error Processingcdev-owner = THIS_MODULE;cdev-ops = &st_fops; cdev_init:用于初始化一个静态分配的cdev对象,一般这个对象会嵌入到其他的对象中。cdev_init会自动初始化ops数据,因此应用程序只需要显示的给owner对象赋值。cdev_init的功能与cdev_alloc基本相同,唯一的区别是cdev_init初始化一个已经存在的cdev对象,并且这中初始化会影响到字符设备删除函数(cdev_del)的行为,请参考cdev_del函数。 cdev_add:向内核系统中添加一个新的字符设备cdev,并且使它立即可用。 cdev_del:从内核系统中移除cdev字符设备。如果字符设备是由cdev_alloc动态分配的,则会释放分配的内存。 cdev_put:减少模块的引用计数,一般很少会有驱动程序直接调用这个函数。 文件操作对象Linux中的所有设备都是文件,内核中用“struct file”结构来表示一个文件。尽管我们的驱动不会直接使用这个结构中的大部分对象,其中的一些数据成员还是很重要的,我们有必要在这里做一些介绍,具体的内容请参考内核源代码树头文件。/ struct file 中的一些重要数据成员const struct file_operations *f_op;unsigned int f_flags;mode_t f_mode;loff_t f_pos;struct address_space *f_mapping;这里我们不对struct file做过多的介绍,在后面的Linux虚拟文件系统中我们在做详细介绍。这个结构中的f_ops成员是我们的驱动所关心的,它是一个struct file_operations结构。Linux里的struct file_operations结构描述了一个文件操作需要的所有函数,它定义在头文件中。struct file_operations struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char _user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock *);这是一个很大的结构,包含了所有的设备操作函数指针。当然,对于一个驱动,不是所有的接口都需要来实现的。对于一个字符设备来说,一般实现open、release、read、write、mmap、ioctl这几个函数就足够了。这里需要指出的是,open和release函数的第一个参数是一个struct inode对象。这个一个内核文件系统索引节点对象,它包含了内核在操作文件或目录是需要的全部信息。对于字符设备驱动来说,我们关心的是从struct inode对象中获取设备号(inode的i_rdev成员)内核提供了两个函数来做这件事。static inline unsigned iminor(const struct inode *inode) return MINOR(inode-i_rdev);static inline unsigned imajor(const struct inode *inode) return MAJOR(inode-i_rdev);尽管我们可以直接从inode-i_rdev获取设备号,但是尽量不要这样做。我们推荐你调用内核提供的函数来获取设备号,这样即使将来inode-i_rdev有所变化,我们的程序也会工作的很好。新的驱动我们已经了解了Linux字符驱动程序的知识点,下面我们就用上面学到的知识来构建一个字符驱动程序。前面我们的“Hello World”程序仅仅是在驱动注册和注销的时候打印“Hello world!”消息,这里我们将通过用户应用程序来读到这个消息,甚至我们可以修改从驱动读到的信息。这个驱动的读写逻辑是很简单的,几乎没有什么逻辑,但它足以满主现在的需要了。首先还是来浏览我们新的驱动,并编译运行这个驱动看看运行效果,最后我们对这个驱动做个详细的介绍。/ hello.c#include #include #include #include #include #include #define DEFAULT_MSG Hello World!n#define MAXBUF 20static unsigned char hello_bufMAXBUF;static int hello_open (struct inode *inode, struct file *filp);static int hello_release (struct inode *inode, struct file *filp);static ssize_t hello_read (struct file *filp, char _user *buf, size_t count, loff_t *pos);static ssize_t hello_write (struct file *filp, const char _user *buf, size_t count, loff_t *pos);static int hello_open (struct inode *inode, struct file *filp) return 0;static int hello_release (struct inode *inode, struct file *filp) return 0;static ssize_t hello_read (struct file *filp, char _user *buf, size_t count, loff_t *pos) int size = count MAXBUF ? count : MAXBUF; printk(hello: Read Hello World !n); if (copy_to_user(buf, hello_buf, size) return -ENOMEM; return size;static ssize_t hello_write (struct file *filp, const char _user *buf, size_t count, loff_t *pos) int size = count dev, 2); return -ENOMEM; hello_cdev-ops = &hello_fops; hello_cdev-owner = THIS_MODULE; error = cdev_add(hello_cdev, dev, 1); if (error) printk(hello: cdev_add failed!n); unregister_chrdev_region(hello_cdev-dev, 2); cdev_del(hello_cdev); return error; memset (hello_buf, 0, sizeof(hello_buf); memcpy(hello_buf, DEFAULT_MSG, sizeof(DEFAULT_MSG); printk(hello: Hello World!n); return 0;static void _exit hello_exit(void) unregister_chrdev_region(hello_cdev-dev, 2); cdev_del(hello_cdev); printk(hello: Goodbye Worldn);module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(GPL);编译并测试这个驱动程序:#需要root权限来运行#make#insmod helloworld.ko#获取设备的设备号并创建设备节点#cat /proc/devices.254 hello#mknod /dev/hello c 254 0#读写测试这个驱动#cat /dev/helloHello World!.#echo My World! /dev/hello#cat /dev/helloMy World!. 初始化函数 这个驱动程序会向系统添加一个新的字符设备hello: static struct cdev *hello_cdev;首先,在我们向内核系统添加我们的设备之前,先向内核系统为我们的设备动态申请设备号,不要忘记这个设备号会显示在/proc/devices列表里: error = alloc_chrdev_region(&dev, 0, 2, hello); if (error) printk(hello: alloc_chardev_region failed!n); return error; 申请到设备号后,我们就可以向系统添加我们的字符设备hello了,这里我们动态分配了字符设备对象hello_cdev: hello_cdev = cdev_alloc(); if (hello_cdev = NULL) printk(hello: alloc cdev failed!n); unregister_chrdev_region(hello_cdev-dev, 2); return -ENOMEM; hello_cdev-ops = &hello_fops; hello_cdev-owner = THIS_MODULE; error = cdev_add(hello_cdev, dev, 1); if (error) printk(hello: cdev_add failed!n); unregister_chrdev_region(hello_cdev-dev, 2); cdev_del(hello_cdev); return error; 操作函数 我们的字符设备的操作函数对象是hello_fops,它的定义如下: static struct file_operations hello_fops = .read = hello_read, .write = hello_write, .open = hello_open, .release = hello_release,;这里我们的open和release函数什么也没有做,仅仅是返回成功(返回0)。read和write函数会把设备内存数据拷贝到用户空间或根据用户空间的数据修改设备内存。我们的设备内存是一个固定大小的数组,共20个字节。这个read和write函数调用了两个新的接口copy_to_user和copy_from_user。这两个函数用同用户空间交换数据。Linux系统使用虚拟地址空间管理内存,分为用户空间和内核空间。一般是这样划分的:用户地址空间是0-3G,内核地址空间是3-4G。用户的应用程序只能运行在用户空间,它不能直接访问内核地址空间的数据,同样内核地址空间的程序也不能直接访问用户空间的数据。因此内核提供函数接口来同用户空间应用程序交换数据,他们定义在同文件中。unsigned long _must_check copy_to_user(void _user *to, const void *from, unsigned long n);unsigned long _must_check copy_from_user(void *to, const void _user *from, unsigned long n); 注销函数 注销函数会向系统注销我们的字符设备,并释放在初始化函数中申请到的空间: unregister_chrdev_region(hello_cdev-dev, 2); cdev_del(hello_cdev);后记在这一章里,我们为我们的“Hello World”驱动添加字符驱动接口,并实现了读写操作,尽管它的读写逻辑还有些问题,不过并不影响我们对于字符驱动的理解(这完全是驱动实现逻辑上的问题了,也许我们的驱动就是这个设计的:) )。现在我们清楚的知道了一个字符设备是如何注册的以及是如何的工作的了,你已经可以设计自己的字符设备驱动了。不妨实现自己的驱动来看看,比如从内核获取一些数据。也许你会问:为什么每次的读写操作都是从设备内存的第一个字节开始?简单的回答就是我们无法保证应用程序的设备内存的同步操作。因此我们把问题简单化了,这样就不会存在同步的问题了。那么到底内核是如何实现同步的呢?我们会在下一章做详细的介绍,同时扩展我们的“Hello World”驱动,并实现真正的按流的方式操作设备。第四章 内核同步技术星期四, 06/10/2010 - 22:32 william 序言就像我们在操作系统里学习的那样,如果多个程序(进程或线程)同时访问临界区数据就会发生竞争。存在竞争条件的程序会产生不可预料的结果。消除竞争的方法一般就是同步的访问临界区数据(原子访问)。Linux内核提供了多种技术用来实现内核同步操作。下面我们就分别介绍。内核同步技术Linux内核是多进程、多线程的操作系统,它提供了相当完整的内核同步方法。作为一个总结,我们先列出内核同步方法列表,这样我们可以从总体上对内核同步技术有个了解,然后我们这分别对每个同步技术做详细介绍。同步技术同步技术描述自旋锁读写自旋锁信号量读写信号
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论