荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: autodotcom (cpu8086), 信区: Linux
标  题: 设备分析2
发信站: 荔园晨风BBS站 (Sat Apr 27 13:11:51 2002), 转信

发信人: fist (星仔迷), 信区: SysInternals
标  题: Linux 设备分析(2)
发信站: 武汉白云黄鹤站 (Wed Dec 13 10:22:06 2000), 站内信件


五. 添加一个字符设备
作为对linux设备管理的分析的总结,我们介绍一下如何添加一个设备,首先介绍
如何添
加一个字符设备。在后面的文章中,我们将新添加的设备称为新设备,说明以我们
实现
的虚拟的字符设备为例,步骤基本如下:
1. 确定设备的设备名称和主设备号:
我们必须找一个还没有被使用的主设备号,分配给自己的字符设备。假设主设备号
为30
(在2.0.34的内核中还没有以30作为主设备号的字符设备)。
2. 确定编写需要的file_operations中的操作函数,包括:
static int my_open(struct inode * inode,struct file * file)
//通过宏指令MINOR()提取inode参数的I_rdev字段,确定辅助设备号,然后
检查相应的读写忙标志,看新设备是否已经打开。如果是,返回错误信息;
否则置读写忙标志为true,阻止再次打开新设备。
static void my_release(struct inode * inode,struct file * file)
//同my_open类似,只是置读写忙标志为false,允许再次打开新设备。
static int my _write(struct inode * inode,struct file * file,const
char * bu
ffer,int count)
//用于对该设备的写
static int my _read(struct inode * inode , struct file * file,char *
buffer,
 int count)
//用于对该设备的读
static int my_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
                //用于传送特殊的控制信息给设备驱动程序,或者长设备驱动程
序取得状
态信息,
在我们实现的虚拟字符设备中,这个函数的功能是用来打开和关闭跟踪功
能。
3. 确定编写需要的初始化函数:
        void my_init(void)
//首先需要将上述的file_operations中的操作函数的地址赋给某个
file_operations
的结构变量my_fops中的相应域;
然后调用标准内核函数登记该设备:register_chrdev(30,"mychd",&my_fops);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
最后对必要的变量(例如读写忙标志、跟踪标志等)赋初值。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4. 在drivers/char/mem.c中添加相应语句;
在chr_dev_init函数之前添加drgn_init的原型说明:
void my_init (void);
在chr_dev_init函数的return语句之前添加以下语句:
my_init ();    //用于在字符设备初始化时初始化新设备
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5. 修改drivers/char/Makefile;
~~~~~~~~~~~~~~~~~~~~~~~~~~
假设我们把所以必要的函数写mychd.c中,则找到"L_OBJS   := tty_io.o n_tty.o
 con
sole.o \"行,将"mychd.o"加到其中。
6. 将该设备私有的*.c,*.h复制到目录drivers/char下。
7. 用命令:make clean;make dep;make zImage重新编译内核。
8. 用mknod命令在目录/dev下建立相应主设备号的用于读写的特殊文件。
完成了上述步骤,你在linux环境下编程时就可以使用新设备了。
六. 添加一个块设备
接下来我们将介绍如何添加一个块设备。在后面的文章中,我们将新添加的块设备
称为
新设备,块设备的添加过程和字符设备有相似之处,我们将主要介绍其不同点,步
骤基
本如下:
1. 确定设备的设备名称和主设备号
我们必须找一个还没有被使用的主设备号,分配给自己的新设备。假设主设备号为
30(
在2.0.34的内核中还没有以30作为主设备号的块设备),则需要在
include/linux/majo
r.h中加入如下句:
                        #define MY_MAJOR 30
这样我们可以通过MY_MAJOR来确定设备为新设备,保证通用性。
2. 确定编写需要的file_operations中的操作函数:
static int my_open(struct inode * inode,struct file * file)
static void my_release(struct inode * inode,struct file * file)
static int my_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
由于使用了高速缓存,块设备驱动程序就不需要包含自己的read()、write()和
fsync()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
函数,但必须使用自己的open()、 release()和 ioctl()函数,这些函数的作用和
字符
设备的相应函数类似。
3. 确定编写需要的输入/输出函数:
static int my _read(void)               //正确处理时返回值为1,错误时返
回值为0
static int my _write(void)      //正确处理时返回值为1,错误时返回值为0
值得注意的是这两个函数和字符设备中的my read()、mywrite()函数不同:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 参数不同:字符设备中的函数是带参数的,用于对其工作空间(也可以看成是简
单的
~~~~~~~~~~~`
缓冲区)中的一定长度(长度也是参数传递的)的字符进行读写;而块设备的函数
是没
有参数的,它通过当前请求中的信息访问高速缓存中的相应的块,因此不需要参数
输入

2 调用形式不同:字符设备中的函数地址是存放在file_operations中的,在对字
符设备
~~~~~~~~~~~~~~
进行读写时就调用了;而块设备的读写函数是在需要进行实际I/O时在request中调
用。
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
这在后面可以看到。
4. 确定编写需要的请求处理函数:
~~~~~~~~~~~~~~~~~~~~~~~~~
static void my_request(void)
在块设备驱动程序中,不带中断服务子程序的请求处理函数是简单的,典型的格式
如下

static void my_request(void)
{
loop:
INIT_REQUEST;

        if (MINOR(CURRENT->dev)>MY_MINOR_MAX)
        {
                end_request(0);
                goto loop;
        }

        if (CURRENT ->cmd==READ)
//CUREENT是指向请求队列头的request结构指针
        {
                end_request(my_read());         //my_read()在前面已经定

                goto loop;
        }

        if (CURRENT ->cmd==WRITE)
        {
                end_request(my_write());        //my_write()在前面已经定

                goto loop;
        }

        end_request(0);
        goto loop;
}
实际上,一个真正的块设备一般不可能没有中断服务子程序,另外设备驱动程序是
在系
统调用中被调用的,这时由内核程序控制CPU,因此不能抢占,只能自愿放弃;因
此驱动
程序必须调用sleep_on()函数,释放对CPU的占用;在中断服务子程序将所需的数
据复制
到内核内存后,再由它来发出wake_up()调用,
5. 如果需要,编写中断服务子程序
实际上,一个真正的块设备一般不可能没有中断服务子程序,另外设备驱动程序是
在系
统调用中被调用的,这时由内核程序控制CPU,因此不能抢占,只能自愿放弃;因
此驱动
程序必须调用sleep_on()函数,释放对CPU的占用;在中断服务子程序将所需的数
据复制
到内核内存后,再由它来发出wake_up()调用。
另外两段中断服务子程序都要访问和修改特定的内核数据结构时,必须要仔细协调
,以
防止出现灾难性的后果。
2 首先,在必要时可以禁止中断,这可以通过sti()和cli()来允许和禁止中断请求

2 其次,修改特定的内核数据结构的程序段要尽可能的短,使中断不至于延时过长

含有中断服务子程序的块设备驱动程序的编写相对比较复杂,我们还没有完全实现
,主
要问题是在中断处理之间的协调。因为这些程序是要加入内核的,系统默认为你是
完全
正确的,如果引起循环或者中断长时间不响应,结果非常严重。我们正在努力实现
这一
程序。
6. 确定编写需要的初始化函数:
        void my_init(void)
需要将的file_operations中的操作函数的地址赋给某个file_operations的结构变
量my
_fops中的相应域;一个典型的形式是:
                struct file_operations my_fops=
                {
                        0,
                        block_read,
                        block_write,
                        0,
                        0,
                        my_ioctl,
                        0,
                        my_open,
                        my_release,
                        block_fsync,
                        0,
                        0,
                        0,
                }
my_init中需要作的工作有:
2 首先调用标准内核函数登记该设备:
        register_chrdev(MY_MOJOR,"my--bdev",&my_fops);
2 将request()函数的地址告诉内核:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
blk_dev[MY_MAJOR].request_fn=DEVICE_REQUEST;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~






DEVICE_REQUEST是请求处理函数的地址,它的定义将在稍后可以看到。
2 告诉新设备的高速缓存的数据块的块大小:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
my_block_size=512;      //也可以是1024等
blksize_size[MY_MAJOR]=& my_block_size;
为了系统在初始化时能够对新设备进行初始化,需要在blk_dev_init()中添加一行
代码
,可以插在blk_dev_init()中return 0的前面,格式为:
        my_init();
7. 在include/linux/blk.h中添加相应语句;
到目前为止,除了DEVICE_REQUEST符合外,还没有告诉内核到那里去找你的
request()函
数,为此需要将一些宏定义加到blk.h中。在blk.h中找到类似的一行:
        #endif  /*MAJOR_NR==whatever    */
在这行前面加入如下宏定义:
        #elif (MAJOR_NR==whatever)

static void my_request(void);
#define DEVICE_NAME "MY_BLK_DEV"                //驱动程序名称
#define DEVICE_REQUEST  my_request              //request()函数指针
#define DEVIEC_NR(device) (MINOR(device))       //计算实际设备号
#define DEVIEC_ON(device)                                       //用于需
要打开的
设备
#define DEVIEC_OFF(device)                              //用于需要关闭的
设备
8. 修改drivers/block/Makefile;
假设我们把所以必要的函数写mybd.c中,则找到"L_OBJS   := tty_io.o n_tty.
o cons
ole.o \"行,将"mybd.o"加到其中。

10. 用命令:make clean;make dep;make zImage重新编译内核。
11. 用mknod命令在目录/dev下建立相应主设备号的用于读写的特殊文件。
完成了上述步骤,你在linux环境下编程时就可以使用新设备了。
七. 一个虚拟的字符设备驱动程序
以下是一个虚拟的字符设备驱动程序,该程序是我和潘刚同学的试验结果,本来我
们还
打算写一个虚拟的块设备驱动程序,由于时间关系,没有能够完全明白中断中断服
务子
程序的编写方法,因此没有没有能够实现一个可以所有的虚拟的块设备,非常遗憾
。不
过主要步骤已经在上文中进行了介绍,我想再有一段时间应该能够完成,到时候一
定交
给李老师看一下。
虚拟的字符设备驱动程序如下,在潘刚同学的试验报告中也有介绍:
/* drgn.h */
#ifdef  KERNEL
#define TRACE_TXT(text) {if(drgn_trace) {console_print(text);
console_print("
\n");}}
#define TRACE_CHR(chr) {if(drgn_trace) console_print(chr);}
#define DRGN_READ  1
#define DRGN_WRITE 0
#endif
#define FALSE 0
#define TRUE  1
#define MAX_BUF   120
#define DRGN_TRON  (('M' << 8)|0x01)
#define DRGN_TROFF (('M' << 8)|0x02)
struct drgn_buf
{
   int buf_size;
   char buffer[MAX_BUF];
   struct drgn_buf *link;
};
/* drgn.c */
#define KERNEL
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/irq.h>
#include "drgn.h"
static int drgn_trace;
static int write_busy;
static int read_busy;
static struct drgn_buf * qhead;
static struct drgn_buf * qtail;
static int  drgn_read(struct inode * , struct file * , char * , int );
static int  drgn_write(struct inode * , struct file * , const char *,
int );

static int  drgn_ioctl(struct inode * , struct file * , unsigned int ,
unsig
ned long );
static int  drgn_open(struct inode *,struct file *);
static void drgn_release(struct inode *,struct file *);
/* extern void console_print(char *);*/
struct file_operations drgn_fops=
{
   NULL,
   drgn_read,
   drgn_write,
   NULL,
   NULL,
   drgn_ioctl,
   NULL,
   drgn_open,
   drgn_release,
   NULL,
   NULL,
   NULL,
   NULL
};
void drgn_init(void)
{
   drgn_trace=TRUE;
   if(register_chrdev(30,"drgn",&drgn_fops))
      TRACE_TXT("Cannot register drgn driver as major device 30.")
   else
      TRACE_TXT("Tiny devie driver registered successfully.")
   qhead=0;
   write_busy=FALSE;
   read_busy=FALSE;
 /*  drgn_trace=FALSE;*/
   return;
}
static int drgn_open(struct inode * inode,struct file * file)
{
   TRACE_TXT("drgn_open")
   switch (MINOR(inode->i_rdev))
   {
      case DRGN_WRITE:
         if(write_busy)
            return -EBUSY;
         else{
            write_busy=TRUE;
            return 0;
         }
      case DRGN_READ:
         if(read_busy)
            return -EBUSY;
         else{
            read_busy=TRUE;
            return 0;
         }
      default:
         return -ENXIO;
   }
}
static void drgn_release(struct inode * inode,struct file * file)
{
   TRACE_TXT("drgn_release")
   switch (MINOR(inode->i_rdev))
   {
      case DRGN_WRITE:
         write_busy=FALSE;
         return;
      case DRGN_READ:
         read_busy=FALSE;
         return;
   }
}
static int drgn_write(struct inode * inode,struct file * file,
                        const char * buffer,int count)
{
   int i,len;
   struct drgn_buf * ptr;
   TRACE_TXT("drgn_write")
   if (MINOR(inode->i_rdev)!=DRGN_WRITE)
      return -EINVAL;
   if ((ptr=kmalloc(sizeof(struct drgn_buf),GFP_KERNEL))==0)
      return -ENOMEM;
   len=count < MAX_BUF?count:MAX_BUF;
   if (verify_area(VERIFY_READ,buffer,len))
      return -EFAULT;
   for(i=0;i < count && i<MAX_BUF;++i)
   {
      ptr->buffer[i]=(char) get_user((char*)(buffer+i));
      TRACE_CHR("w")
   }
   ptr->link=0;
   if(qhead==0)
      qhead=ptr;
   else
      qtail->link=ptr;
   qtail=ptr;
   TRACE_CHR("\n")
   ptr->buf_size=i;
   return i;
}
static int drgn_read(struct inode * inode , struct file * file,
                         char * buffer, int count)
{
   int i,len;
   struct drgn_buf * ptr;
   TRACE_TXT("drgn_read")
   if(MINOR(inode->i_rdev)!=DRGN_READ)
      return -EINVAL;
   if (qhead==0)
      return -ENODATA;
   ptr=qhead;
   qhead=qhead->link;
   len=count < ptr->buf_size?count:ptr->buf_size;
   if (verify_area(VERIFY_WRITE,buffer,len))
      return -EFAULT;
   for (i=0; i<count && i<ptr->buf_size; ++i)
   {
      put_user((char) ptr->buffer[i],(char *)(buffer+i));
      TRACE_CHR("r")
   }
   TRACE_CHR("\n")
   kfree_s(ptr,sizeof(struct drgn_buf));
   return i;
}
static int drgn_ioctl(struct inode * inode, struct file * file,
                         unsigned int cmd, unsigned long arg)
{
   TRACE_TXT("drgn_ioctl")
/*   if (cmd==DRGN_TRON){
       drgn_trace=TRUE;
       return 0;
   }
   else
       if (cmd==DRGN_TROFF){
           drgn_trace=FALSE;
           return 0;
       }
       else
           return -EINVAL;*/
   switch(cmd)
   {
      case DRGN_TRON:
         drgn_trace=TRUE;
         return 0;
      case DRGN_TROFF:
         drgn_trace=FALSE;
         return 0;
      default:
         return -EINVAL;
   }
   else
       if (cmd==DRGN_TROFF){
           drgn_trace=FALSE;
           return 0;
       }
       else
           return -EINVAL;*/
   switch(cmd)
   {
      case DRGN_TRON:
         drgn_trace=TRUE;
         return 0;
      case DRGN_TROFF:
         drgn_trace=FALSE;
         return 0;
      default:
         return -EINVAL;
   }
}

--
※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 202.114.1.60]



--
-再见了
-我爱的那个人
-从此,我将与DDK独行

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.55.48]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店