Chinaunix首页 | 论坛 | 博客
  • 博客访问: 82094
  • 博文数量: 19
  • 博客积分: 575
  • 博客等级: 中士
  • 技术积分: 203
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-03 20:06
个人简介

好好学习,天天向上

文章分类

全部博文(19)

分类: LINUX

2011-04-08 12:08:55

基本的字符驱动

1.获取设备号

设备编号是一个32长度的整型:

linux/types.h

typedef u_long dev_t;

其中高22位为主设备号,低8位为次设备号,使用下面宏可以得到设备号

linux/kdev_t.h

#define MAJOR(dev)  ((dev)>>8)

#define MINOR(dev)  ((dev) & 0xff)

#define MKDEV(ma,mi)  ((ma)<<8 | (mi))

 

设备号有什么用?

LDD3上说主设备号标识驱动,次设备号标识设备。

我自己的理解:应用程序通过访问/dev目录下的设备文件来访问驱动,比如对一个设备文件进行read,设备文件通过自己的主设备号,在内核的驱动链表中查找到驱动入口,然后再调用驱动的read

所以,字符驱动首先要得到一个设备号,这样才能被应用程序找到。

 

得到设备号的办法,一个是自己手动设定,比如看到26这个主设备号没人用,那么就可以使用这个做为我们的驱动主设备号了,另一个方法是,不知道哪个号码有人用,懒得去看,那么可以请求内核分配一个。这两种方法各有优缺点,第一个方法创建设备文件时简单,但不能保证在其他系统中这个设备号也没驱动在用,会使用系统的设备号比较混乱,注册时可能导致失败,第二个方法,创建设备文件时复杂。 推荐使用第二个方法。

动态分配设备号:

linux/fs.h

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char *name);

dev用来返回分配的设备号

baseminor是次设备号的起始值,通常为0

count 是你请求的连续设备编号的总数.

name 是应当连接到这个编号范围的设备的名字,它会出现在 /proc/devices sysfs .

函数成功返回0,失败返回负的错误代码

当你的驱动从内核卸载时,当然得把设备号还回去,使用下面的函数,一般在模块退出时使用:

void unregister_chrdev_region(dev_t first, unsigned int count);

 

测试:

base_char.c

#include

#include

#include

#include

 

dev_t dev_major;

 

static int __init base_char_init(void)

{

  dev_t dev;

  if(alloc_chrdev_region(&dev,0,1,”base_char”)<0)

  {

printk(“base_char alloc dev id failed!\n”);

dev_major=0;

goto error0;

  }

  dev_major=MAJOR(dev);

  printk(“base_char devmajor is %d\n”,dev_major);

  return 0;

error0:

  return -1;

}

 

static void __exit base_char_exit(void)

{

  if(dev_major!=0)

unregister_chrdev_region(MKDEV(dev_major,0),1);

}

 

module_init(base_char_init);

module_exit(base_char_exit);

 

MODULE_AUTHOR(“crazycode”);

Makefile:

obj-m := base_char.o

KDIR := /home/linux-2.6.9

PWD := $(shell pwd)

default:

  $(MAKE) –C $(KDIR) M=$(PWD) modules

.PHONY:clean

clean:

  -rm *.o *.ko *.mod.c

 

make

insmod base_char.ko

cat /proc/devices

254 base_char

可以看到主设备号为254

2.创建设备文件/设备节点

       得到设备号了,接下来就可以创建设备文件了,创建设备文件的方法是调用mknod工具:

       mknod /dev/base_char –m 666 –c 254 0

-m 666 表示文件的权限位为666

-c 254 0 表示主设备号为254,次设备号为0

 

       使用awk脚本读取/proc/devices中的设备号可以自动创建设备文件。

 

3.字符驱动的注册

       应用程序和驱动的交互,内核提供的接口是设备文件,应用程序通过open,read,write等基本的文件操作来访问设备文件。最终这些操作是调用了驱动中我们提供的函数,内核怎么知道驱动的?这需要我们来注册它,这个步骤就是字符驱动的注册。

       字符驱动的注册,简单地说,就是告诉内核一些驱动的信息,然后内核保存这些信息以便后面使用,内核用一个结构体来记录这些信息:

linux/cdev.h

struct cdev {

  struct kobject kobj;

  struct module *owner;

  struct file_operations *ops;

  struct list_head list;

  dev_t dev;

  unsigned int count;

};

owner模块的所有者,ops一组自定义的文件操作函数,dev起始设备号,count关联到设备的设备号数目,里面的list告诉我们,这个结构体会被放在一个循环链表中,事实也正是如此。当一个驱动拥有多个设备(cout>1)的时候,注册的时候,内核会增加多个cdev结构体,也就是说一个设备一个结构体。

这个结构体代表着内核中的一个字符设备,每个字符设备都对应着这样一个结构体。

内核提供了一些函数用来初始化cdev结构体,以及添加cdev到内核,它们是:

linux/cdev.h

struct cdev *cdev_alloc(void);//动态申请,也可以自己静态定义的

void cdev_init(struct cdev *cdev, struct file_operations *fops);//初始化

int cdev_add(struct cdev *p, dev_t dev, unsigned count);//注册

void cdev_del(struct cdev *p);//从内核中删除

 

下面是初始化要的文件操作函数结构体:

linux/fs.h

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 (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);

  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

  ssize_t (*aio_write) (struct kiocb *, const char __user *, 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 datasync);

  int (*aio_fsync) (struct kiocb *, int datasync);

  int (*fasync) (int, struct file *, int);

  int (*lock) (struct file *, int, struct file_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 *);

  ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);

  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 *);

};

 

测试:

base_char.c

#include

#include

#include

#include

#include

 

dev_t dev;

struct cdev cdev;

struct file_operations fop;

 

static int __init base_char_init(void)

{

    if(alloc_chrdev_region(&dev,0,1,"base_char")<0)

    {

        printk("base_char alloc dev id failed!\n");

        goto error0;

    }

    fop.owner=THIS_MODULE;

    cdev_init(&cdev,&fop);

    cdev.owner=THIS_MODULE;

    if(cdev_add(&cdev,dev,1)<0)

    {

        printk("base_char cdev add failed!\n");

        goto error1;

    }

    printk("base char register successful!\n");

    return 0;

error1:

    unregister_chrdev_region(dev,1);

error0:

    return -1;

}

 

static void __exit base_char_exit(void)

{

    cdev_del(&cdev);

    unregister_chrdev_region(dev,1);

}

 

module_init(base_char_init);

module_exit(base_char_exit);

 

MODULE_AUTHOR("crazycode");

MODULE_LICENSE("GPL");

 

上面的例子并没有添加文件操作函数,下面我们给它加上open,release,read.write,构成一个基本的字符驱动:

base_char.c中增加四个函数:

ssize_t base_char_read(struct file *fp, char __user *buf, size_t size, loff_t *ff)

{

    printk("base char read!\n");

    return 0;

}

ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff)

{

    printk("base char write!\n");

    return 0;

}

int base_char_open(struct inode *inode, struct file *fp)

{

    printk("base char open!\n");

    return 0;

}

int base_char_release(struct inode *inode, struct file *fp)

{

    printk("base char close!\n");

    return 0;

}

 

struct file_operations fop=

{

  .owner=THIS_MODULE,

  .open=base_char_open,

  .write=base_char_write,

  .read=base_char_read,

  .release=base_char_release

};

接下来写个测试的应用程序:test.c

#include

#include

#include

#include

#include

 

int main()

{

    int fd=open("/dev/base_char",O_RDONLY);

    if(fd<0)

    {

        perror("");

        return -1;

    }

    char buf[10];

    read(fd,buf,10);

    write(fd,buf,10);

    close(fd);

    return 0;

}

编译:

$ gcc -o test test.c

加载模块

$ sudo insmod base_char.ko

查看设备号:

$ cat /proc/devices

250 base_char

创建设备文件:

sudo mknod /dev/base_char -m 666 c 250 0

测试:./test

可以看到系统日志里面显示了下面信息,说明应用程序已经成功调用了驱动。

Apr  6 21:48:21 ubuntu kernel: [112431.976741] base char register successful!

Apr  6 21:50:43 ubuntu kernel: [112573.379065] base char open!

Apr  6 21:50:43 ubuntu kernel: [112573.379106] base char read!

Apr  6 21:50:43 ubuntu kernel: [112573.379142] base char close!

 

回过头来,看看open等函数的参数,发现两个新的结构体,struct inodestruct fileinode结构体定义如下:

linux/fs.h

struct inode {

  struct hlist_node i_hash;

  struct list_head  i_list;

  struct list_head  i_dentry;

  unsigned long   i_ino;

  atomic_t    i_count;

  umode_t     i_mode;

  unsigned int    i_nlink;

  uid_t     i_uid;

  gid_t     i_gid;

  dev_t     i_rdev;

  loff_t      i_size;

  struct timespec   i_atime;

  struct timespec   i_mtime;

  struct timespec   i_ctime;

  unsigned int    i_blkbits;

  unsigned long   i_blksize;

  unsigned long   i_version;

  unsigned long   i_blocks;

  unsigned short          i_bytes;

  unsigned char   i_sock;

  spinlock_t    i_lock; /* i_blocks, i_bytes, maybe i_size */

  struct semaphore  i_sem;

  struct rw_semaphore i_alloc_sem;

  struct inode_operations *i_op;

  struct file_operations  *i_fop; /* former ->i_op->default_file_ops */

  struct super_block  *i_sb;

  struct file_lock  *i_flock;

  struct address_space  *i_mapping;

  struct address_space  i_data;

#ifdef CONFIG_QUOTA

  struct dquot    *i_dquot[MAXQUOTAS];

#endif

/* These three should probably be a union */

  struct list_head  i_devices;

  struct pipe_inode_info  *i_pipe;

  struct block_device *i_bdev;

  struct cdev   *i_cdev;

  int     i_cindex;

 

  __u32     i_generation;

 

  unsigned long   i_dnotify_mask; /* Directory notify events */

  struct dnotify_struct *i_dnotify; /* for directory notifications */

 

  unsigned long   i_state;

  unsigned long   dirtied_when; /* jiffies of first dirtying */

 

  unsigned int    i_flags;

 

  atomic_t    i_writecount;

  void      *i_security;

  union {

    void    *generic_ip;

  } u;

#ifdef __NEED_I_SIZE_ORDERED

  seqcount_t    i_size_seqcount;

#endif

};

这个结构体,是文件在内核中的体现,它对于每个文件来说是唯一的,当系统要打开一个文件,如果该文件在内核中已经拥有了inode节点,那么仅创建struct file和设置一些参数,如果还没有该文件的inode节点,那么就新建一个inode节点。这个结构只有 2 个成员对于编写驱动代码有用:

dev_t i_rdev; 对于代表设备文件的节点, 这个成员包含实际的设备编号.

struct cdev *i_cdev; cdev结构体代表字符设备

inode节点中获取主次设备号:

static inline unsigned iminor(struct inode *inode);

static inline unsigned imajor(struct inode *inode);

 

再看struct file

struct file {

  struct list_head  f_list;

  struct dentry   *f_dentry;

  struct vfsmount         *f_vfsmnt;

  struct file_operations  *f_op;

  atomic_t    f_count;

  unsigned int    f_flags;

  mode_t      f_mode;

  int     f_error;

  loff_t      f_pos;

  struct fown_struct  f_owner;

  unsigned int    f_uid, f_gid;

  struct file_ra_state  f_ra;

 

  unsigned long   f_version;

  void      *f_security;

 

  /* needed for tty driver, and maybe others */

  void      *private_data;

 

#ifdef CONFIG_EPOLL

  /* Used by fs/eventpoll.c to link all the hooks to this file */

  struct list_head  f_ep_links;

  spinlock_t    f_ep_lock;

#endif /* #ifdef CONFIG_EPOLL */

  struct address_space  *f_mapping;

};

file结构体代表着一个打开的文件 (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭.

这两个传入到open的参数,对于驱动开发来讲有什么用呢?首先应该明确我们在open函数中应当做的事情:

  • 检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误)
  • 如果它第一次打开, 初始化设备
  • 如果需要, 更新 f_op 指针.
  • 分配并填充要放进 filp->private_data 的任何数据结构

要检查设备信息,就要知道打开了哪个设备,这个设备结构体cdev就可以从inode中拿到了。可能有人会问,为什么自己定义的cdev结构体,注册到内核中去,最终又要从内核中拿回来呢,这不多此一举吗?上面说了,如果驱动有多个设备,那么内核中就会有多个此驱动的cdev,所以要重新从内核中拿到正确的cdev

       open中初始化设备信息,好一点的做法是定义一个结构体来保存设备信息,并把cdev放进去,然后将设备信息结构体的指针放到file结构体的private_data中,让后面的read,write等方法使用。

       拿到cdev后怎么得到设备信息结构体指针呢,内核提供了宏:

linux/kernel.h

#define container_of(ptr, type, member) ({      \

        const typeof( ((type *)0)->member ) *__mptr = (ptr);  \

        (type *)( (char *)__mptr - offsetof(type,member) );})

代码可以这样写:

struct dev_info_struct

{

  struct cdev cdev;

}dev_info;

。。。

int base_char_open(struct inode *inode, struct file *fp)

{

  struct dev_info_struct *dev_info;

  dev_info=container_of(inode->i_cdev,struct dev_info_struct,cdev);

  fp->private_data=dev_info;

  printk("base char open!\n");

  return 0;

}

。。。

 

我们的字符驱动到现在什么也没做,现在有必要增加一个设备给它,LDD3上的scull我个人认为其内存模型有些复杂,不适合做些简单的实验,我决定把我的设备定义成这样:一块长度为50字节的内存。驱动加载时,向系统申请一块50字节的内存做为设备,驱动卸载时,释放这些空间。

怎么分配内存,使用malloc?别忘了内核中没有C库的,正解是使用内核提供的函数:

linux/slab.h

static inline void *kmalloc(size_t size, int flags);

void kfree (const void *objp);

其中flags是内存的类型,这里使用GFP_KERNEL,失败返回NULL,记得检查。

修改设备信息结构体:

struct dev_info_struct

{

  char* data;

  int size;

  struct cdev cdev;

}dev_info;

在模块init中分配内存:

dev_info.size=50;

dev_info.data=kmalloc(dev_info.size,GFP_KERNEL);

在模块exit中回收内存:

kfree(dev_info.data);

 

分配了内存之后,来看下write函数:

ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff);

__user表示的是后面的这个指针来自用户空间,内核中不能直接使用

buf是数据指针,size是数据大小,ff是偏移(暂时不管)

用户程序将数据放到buf指向的缓冲区中,然后调用write,驱动只要将buf中的数据复制到设备(内存)中就可以了,但是buf是不能直接使用的,内核提供了这样的函数来使用它:

asm/uaccess.h

unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);

unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);

这两个函数如果成功返回0

 

加入readwrite后的驱动:

base_char.c

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

dev_t dev;

struct dev_info_struct

{

  char* data;

  int size;

  struct cdev cdev;

}dev_info;

 

ssize_t base_char_read(struct file *fp, char __user *buf, size_t size, loff_t *ff)

{

  struct dev_info_struct *dev_info_ptr=(struct dev_info_struct*)fp->private_data;

  printk("base char read!\n");

  if(size<0)

    return -1;

  if(size>dev_info_ptr->size)

    size=dev_info_ptr->size;

  return copy_to_user(buf,dev_info_ptr->data,size);

}

ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff)

{

  struct dev_info_struct *dev_info_ptr=(struct dev_info_struct*)fp->private_data;

  printk("base char write !\n");

  if(size<0)

    return -1;

  if(size>dev_info_ptr->size)

    size=dev_info_ptr->size;

  return copy_from_user(dev_info_ptr->data,buf,size);

}

int base_char_open(struct inode *inode, struct file *fp)

{

  struct dev_info_struct *dev_info_ptr;

  printk("base char open!\n");

  dev_info_ptr=container_of(inode->i_cdev,struct dev_info_struct,cdev);

  fp->private_data=dev_info_ptr;

  return 0;

}

 

int base_char_release(struct inode *inode, struct file *fp)

{

  printk("base char close!\n");

  return 0;

}

 

struct file_operations fop=

{

  .owner=THIS_MODULE,

  .open=base_char_open,

  .write=base_char_write,

  .read=base_char_read,

  .release=base_char_release

};

 

 

static int __init base_char_init(void)

{

  if(alloc_chrdev_region(&dev,0,1,"base_char")<0)

  {

    printk("base_char alloc dev id failed!\n");

    goto error0;

  }

  dev_info.size=50;

  dev_info.data=kmalloc(dev_info.size,GFP_KERNEL);

  if(dev_info.data==NULL)

  {

    printk("kmalloc failed!\n");

    goto error1;

  }

  memset(dev_info.data,0,dev_info.size);

cdev_init(&dev_info.cdev,&fop);

  dev_info.cdev.owner=THIS_MODULE;

  if(cdev_add(&dev_info.cdev,dev,1)<0)

  {

    printk("base_char cdev add failed!\n");

    goto error2;

  }

  printk("base char register successful!\n");

  return 0;

error2:

  kfree(dev_info.data);

error1:

  unregister_chrdev_region(dev,1);

error0:

  return -1;

}

 

static void __exit base_char_exit(void)

{

  kfree(dev_info.data);

  cdev_del(&dev_info.cdev);

  unregister_chrdev_region(dev,1);

}

 

module_init(base_char_init);

module_exit(base_char_exit);

 

MODULE_AUTHOR("crazycode");

MODULE_LICENSE("GPL");

 

测试代码

test.c

#include

#include

#include

#include

#include

 

 

int main()

{

  int fd=open("/dev/base_char",O_RDWR);

  int count=0;

  if(fd<0)

  {

    perror("");

    return -1;

  }

  char buf[50];

  strcpy(buf,"hello world!\n");

  write(fd,buf,50);

  strcpy(buf,"xxxxxxxxxxxxxxx!\n");

  read(fd,buf,50);

  printf("read test: %s\n",buf);

  close(fd);

  return 0;

}

阅读(1869) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~