选择显示字体大小

write的奥秘

linux下我们在使用设备的时候,都会用到write这个函数,通过这个函数我们可以象使
用文件那样向设备传送数据。可是为什么用户使用write函数就可以把数据写到设备里面
去,这个过程到底是怎么实现的呢?

这个奥秘就在于设备驱动程序的write实现中,这里我结合一些源代码来解释如何使得一
个简简单单的write函数能够完成向设备里面写数据的复杂过程。

这里的源代码主要来自两个地方。第一是oreilly出版的《linux device driver》中的
实例,第二是linux kernel 2.2.14核心源代码。我只列出了其中相关部分的内容,如果
读者有兴趣,也可以查阅其它源代码。不过我不是在讲解如何编写设备驱动程序,所以不
会对每一个细节都进行说明,再说有些地方我觉得自己还没有吃透。

由于《linux device driver》一书中的例子对于我们还是复杂了一些,我将其中的一个
例程简化了一下。这个驱动程序支持这样一个设备:核心空间中的一个长度为10的数组
kbuf[10]。我们可以通过用户程序open它,read它,write它,close它。这个设备的名
字我称为short_t。

现在言归正传。
对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式
存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节
点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设
备,一般对应着确定的驱动程序;次设备号一般是区分是标明不同属性,例如不同的使用
方法,不同的位置,不同的操作。这个设备号是从/proc/devices文件中获得的,所以一
般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主
要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一
个设备文件时,操作系统就已经知道这个设备所对应的驱动程序是哪一个了。这个"知道"
的过程后面就讲。

我们再说说驱动程序的基本结构吧。这里我只介绍动态模块型驱动程序(就是我们使用
insmod加载到核心中并使用rmmod卸载的那种),因为我只熟悉这种结构。
模块化的驱动程序由两个函数是固定的:int init_module(void) ;void
cleanup_module(void)。前者在insmod的时候执行,后者在rmmod的时候执行。
init_nodule在执行的时候,进行一些驱动程序初始化的工作,其中最主要的工作有三
件:注册设备;申请i/o端口地址范围;申请中断irq。这里和我们想知道的事情相关的只
有注册设备。

下面是一个典型的init_module函数:

int init_module(void){
int result = check_region(short_base,1);/* 察看端口地址*/
……
request_region(short_base,1,"short"); /* 申请端口地址*/
……
result = register_chrdev(short_major, "short", &short_fops); /* 注册设备
*/
……
result = request_irq(short_irq, short_interrupt, sa_interrupt, "short",
null); /* 申请irq */
……
return 0;
}/* init_module*/

上面这个函数我只保留了最重要的部分,其中最重要的函数是
result = register_chrdev(short_major, "short", &short_fops);
这是一个驱动程序的精髓所在!!当你执行indmod命令时,这个函数可以完成三件大事:
第一,申请主设备号(short_major),或者指定,或者动态分配;第二,在内核中注册设
备的名字("short");第三,指定fops方法(&short_fops)。其中所指定的fops方法就是
我们对设备进行操作的方法(例如read,write,seek,dir,open,release等),如何实现
这些方法,是编写设备驱动程序大部分工作量所在。

现在我们就要接触关键部分了--如何实现fops方法。
我们都知道,每一个文件都有一个file的结构,在这个结构中有一个file_operations的
结构体,这个结构体指明了能够对该文件进行的操作。

下面是一个典型的file_operations结构:
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, 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);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};

我们可以看到它实际上就是许多文件操作的函数指针,其中就有write,其它的我们就不
去管它了。这个write指针在实际的驱动程序中会以程序员所实现的函数名字出现,它指
向程序员实现的设备write操作函数。下面就是一个实际的例子,这个write函数可以向核
心内存的一个数组里输入一个字符串。

int short_write (struct inode *inode, struct file *filp, const char *buf,
int count){
int retval = count;
extern unsigned char kbuf[10];

if(count>10)
count=10;
copy_from_user(kbuf, buf, count);
return retval;
}/* short_write */
设备short_t对应的fops方法是这样声明的:
struct file_operations short_fops = {
null, /* short_lseek */
short_read,
short_write,
null, /* short_readdir */
null, /* short_poll */
null, /* short_ioctl */
null, /* short_mmap */
short_open,
short_release,
null, /* short_fsync */
null, /* short_fasync */
/* nothing more, fill with nulls */
};

其中null的项目就是不提供这个功能。所以我们可以看出short_t设备只提供了
read,write,open,release功能。其中write功能我们在上面已经实现了,具体的实现函
数起名为short_write。这些函数就是真正对设备进行操作的函数,这就是驱动程序的一
大好处:不管你实现的时候是多么的复杂,但对用户来看,就是那些常用的文件操作函数。

但是我们可以看到,驱动程序里的write函数有四个参数,函数格式如下:
short_write (struct inode *inode, struct file *filp, const char *buf, int count)
而用户程序中的write函数只有三个参数,函数格式如下:
write(inf fd, char *buf, int count)
那他们两个是怎么联系在一起的呢?这就要靠操作系统核心中的函数sys_write了,下面
linux kernel 2.2.14中sys_write中的源代码:
asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
{
ssize_t ret;
struct file * file;
struct inode * inode;
ssize_t (*write)(struct file *, const char *, size_t, loff_t *); /* 指向
驱动程序中的wirte函数的指针*/

lock_kernel();
ret = -ebadf;
file = fget(fd); /* 通过文件描述符得到文件指针 */
if (!file)
goto bad_file;
if (!(file->f_mode & fmode_write))
goto out;
inode = file->f_dentry->d_inode; /* 得到inode信息 */
ret = locks_verify_area(flock_verify_write, inode, file, file->f_pos,
count);
if (ret)
goto out;
ret = -einval;
if (!file->f_op !(write = file->f_op->write)) /* 将函数开始时声明的
write函数指针指向fops方法中对应的write函数 */
goto out;
down(&inode->i_sem);
ret = write(file, buf, count, &file->f_pos); /* 使用驱动程序中的write函数
将数据输入设备,注意看,这里就是四个参数了 */
up(&inode->i_sem);
out:
fput(file);
bad_file:
unlock_kernel();
return ret;
}

我写了一个简单的程序来测试这个驱动程序,该程序源代码节选如下(该省的我都省了):

main(){
int fd,count=0;
unsigned char buf[10];
fd=open("/dev/short_t",o_rdwr);
printf("input string:");
scanf("%s",buf);
count=strlen(buf);
if(count>10)
count=10;
count=write(fd,buf,count);
close(fd);
return 1;
}

现在我们就演示一下用户使用write函数将数据写到设备里面这个过程到底是怎么实现的:
1,insmod驱动程序。驱动程序申请设备名和主设备号,这些可以在/proc/devieces中获得。
2,从/proc/devices中获得主设备号,并使用mknod命令建立设备节点文件。这是通过主
设备号将设备节点文件和设备驱动程序联系在一起。设备节点文件中的file属性中指明了
驱动程序中fops方法实现的函数指针。
3,用户程序使用open打开设备节点文件,这时操作系统内核知道该驱动程序工作了,就
调用fops方法中的open函数进行相应的工作。open方法一般返回的是文件标示符,实际
上并不是直接对它进行操作的,而是有操作系统的系统调用在背后工作。
4,当用户使用write函数操作设备文件时,操作系统调用sys_write函数,该函数首先通
过文件标示符得到设备节点文件对应的inode指针和flip指针。inode指针中有设备号信
息,能够告诉操作系统应该使用哪一个设备驱动程序,flip指针中有fops信息,可以告诉
操作系统相应的fops方法函数在那里可以找到。
5,然后这时sys_write才会调用驱动程序中的write方法来对设备进行写的操作。
其中1-3都是在用户空间进行的,4-5是在核心空间进行的。用户的write函数和操作系统
的write函数通过系统调用sys_write联系在了一起。
注意:
对于块设备来说,还存在写的模式的问题,这应该是由gnu c库来解决的,这里不予讨
论,因为我没有看过gnu c库的源代码。
另外,这是一个测试版的文章,请各位朋友们多提意见和建议,非常感谢!   


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons