Linux字符设备驱动(三)

来源:互联网 发布:电影cms系统哪个好 编辑:程序博客网 时间:2024/04/28 09:38

字符设备驱动之体验篇

 

三.字符设备之编程

通过程序来体验字符设备驱动编程的过程

在Linux系统中,字符设备驱动由如下几个部分组成:

(1)字符设备驱动模块加载与卸载函数

(2)字符设备驱动的file_operations结构体中成员函数

file_operations结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux进行系统调用最终的实现着。

(3)在字符设备驱动中,需要定义一个file_operations的实例,并将具体设备驱动的函数赋值给file_operations的成员。

它完成以下的功能:
1.
对设备初始化和释放。
2.
把数据从内核传送到硬件和从硬件读取数据。
3.
读取应用程序传送给设备文件的数据和回送应用程序请求的数据。
4.
检测和处理设备出现的错误。

1.设备驱动的头文件,宏即设备结构体

(1)头文件

#include<linux/kernel.h>

#include<linux/init.h>

#include<linux/module.h> //内核模块编程所必需的

 

#include<linux/cdev.h>

#include<linux/fs.h>//设备编程所必须的

 

#include<asm/io.h>

#include<asm/system.h>

#include<linux/types.h>//

#include<asm/uaccess.h>//设备编程所必须的

 

#include<linux/mm.h>

#include<linux/sched.h>//调度和内存管理所必需的

#include<linux/errno.h> 

(2)宏和全局变量

#define GLOBALMEM_MAJOR 254 //预设的globalmem的主设备号

#define GLOBALMEM_SIZE      0x1000  //全局内存最大为4kb

#define MEM_CLEAR         0x1     //清空全局内存

static globalmem_major = GLOBALMEM_MAJOR  //主设备号

//globalmem设备结构体

struct globalmem_dev

{

   struct cdev cdev; //cdev结构体,设备编程,必须的

   unsigned char mem[GLOBALMEM_SIZE];//全局内存

};

struct globalmem_dev *dev;//设备结构体实例

1>定义globalmem_dev设备结构体包含了对应于globalmem字符设备的cdev,使用的内存mem[GLOBALMEM_SIZE].这样做体现了面向对象程序设计中“封装“的思想。

2.加载和卸载设备驱动

(1)在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,申请设备结构体的内存

static int __init global_init(void)

{

       int result;

       dev_t devno = MKDEV(globalmem_major,0)//保存设备号

       //申请设备号

       if(globalmem_major)

             result = register_chrdev_region(devno,1,"globalmem");

       else{//动态获得主设备号

              result = alloc_chrdev_region(&devno,0,1,"globalmem")

                 globalmem_major =  MAJOR(devno);

       }

       if(result < 0)

              return restult;

      

       dev = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);

        if(!dev)//申请失败

       {

              result = -ENOMEM;

              goto fail_malloc;  

       }

       memset(dev,0,sizeof(struct globalmem_dev));

       globalmem_setup_cdev(devno, 0);//字符设备的注册

       return 0;

fail_malloc :

              unregister_chrdev_region(devno,1);

              return result;

}

(2)在卸载函数中应实现设备号的释放和cdev的注销

static void __exit global_exit(void)

{

   cdev_del(&globalmem_devp->cdev);   /*注销cdev,即从系统中删除此设备*/

   kfree(globalmem_devp);     /*释放设备结构体内存*/

   unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/

}

要先注销字符设备之后,才能释放设备号

(3)globalmem_setup_cdev()函数完成cdev的初始化和添加

static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)

{

       int err;

       dev_t devno = MKDEV(globalmem_major,index);

      

       cdev_init(&dev->cdev,&globalmem_fops);

        dev->cdev.owner = THIS_MODULE;

       dev->cdev.ops = &globalmem_fops;

       err = cdev_add(&dev->cdev, devno, 1);

       if (err)

     printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);

}

1>在获取了设备号范围之后,需要将设备添加到字符设备结构体中,以激活设备。这需要用cdev_init初始化一个sturct cdev的实例,接下来调用cdev_add。在cdev_add成功返回后,设备进入活动状态。

2>在cdev_init()函数中,与globalmem的dev关联的file_operations结构体代码为

static const struct file_operations globalmem_fops =

{

       .owner = THIS_MODULE,

       .llseek = globalmem_llseek,

       .open = globalmem_open,

       .read = globalmem_read,

       .write = globalmem_write,

       .ioctl = globalmem_ioctl,

       .release = globalmem_release,

};

3>申请设备号后在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。

3.file_operations中具体函数的实现

打开的设备在内核内部由file结构标识,内核使用file_operations结构访问驱动程序的函数。下面主要介绍常用的几个成员:

(1)文件打开函数

int globalmem_open(struct inode *inode,struct file *filp)

{

       //将设备结构体指针赋值给文件私有数据指针

       filp->private_data = globalmem_devp;

}

(2)读写函数

globalmem设备驱动的读写函数主要是让设备结构体的mem[]数组与用户空间交互数据,并随着访问的字节数变更返回用户的文件读写偏移位置。

1>读函数

static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)

{

      unsigned long p =  *ppos;

      unsigned int count = size;

      int ret = 0;

              /*获得设备结构体指针*/

              struct  globalmem_dev  *dev  =  filp->private_data;

      /*分析获取有效的写长度*/

      if (p >= GLOBALMEM_SIZE)

      return count ?  - ENXIO: 0;

      if (count > GLOBALMEM_SIZE - p)

      count = GLOBALMEM_SIZE - p;

 

     /*从内核空间向用户空间写数据*/

     if (copy_to_user(buf, (void*)(dev->mem + p), count))

     {

       ret =  - EFAULT;

    }

    else

    {

      *ppos += count;

      ret = count;

     printk(KERN_INFO "read %d bytes(s) from %d/n", count, p);

    }

    return ret;

}

2>写函数

static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)

 {

    unsigned long p =  *ppos;

    unsigned int count = size;

    int ret = 0;

     /*获得设备结构体指针*/

       struct  globalmem_dev  *dev  =  filp->private_data;

    /*分析和获取有效的写长度*/

     if (p >= GLOBALMEM_SIZE)

     return count ?  - ENXIO: 0;

     if (count > GLOBALMEM_SIZE - p)

     count = GLOBALMEM_SIZE - p;

    /*从用户空间向内核空间写数据*/

    if (copy_from_user(dev->mem + p, buf, count))

     ret =  - EFAULT;

    else

   {

     *ppos += count;

     ret = count;

 

    printk(KERN_INFO "written %d bytes(s) from %d/n", count, p);

   }

 

   return ret;

}

(3)文件定位函数

/* seek文件定位函数*/

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)

{

      loff_t ret = 0;

      switch (orig)

      {

           case 0:   /*相对文件开始位置便宜*/

           if (offset < 0)

           {

            ret =  - EINVAL;

                     break;

           }

                   if ((unsigned int)offset > GLOBALMEM_SIZE)

                   {

                 ret =  - EINVAL;

                 break;

                   }

                   //f_pos文件当前的读写位置

                   //SEEK_SET

                   filp->f_pos = (unsigned int)offset;

                   ret = filp->f_pos;

                   break;

                   case 1:   /*相对文件当前位置偏移*/

                   if ((filp->f_pos + offset) > GLOBALMEM_SIZE)

                   {

                       ret =  - EINVAL;

                       break;

                   }

                   if ((filp->f_pos + offset) < 0)

                   {

                       ret =  - EINVAL;

                       break;

                   }

                   /*SEEK_CUR*/

                   filp->f_pos += offset;

                   ret = filp->f_pos;

                   break;

                   default:

                   ret =  - EINVAL;

                   break;

     }

  return ret;

}

(4)ioctl设备控制函数

/*ioctl设备控制函数*/

static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)

{

    /*获得设备结构体指针*/

       struct globalmem_dev *dev = filp->private_data;

    switch (cmd)

    {

       case MEM_CLEAR:

       memset(dev->mem, 0, GLOBALMEM_SIZE);

       printk(KERN_INFO "globalmem is set to zero/n");

       break;

 

       default:

       return  - EINVAL;

   }

   return 0;

}

(5)文件释放函数

int globalmem_release(struct inode *inode,struct file *filp)

{

       return 0;

}

说明:

1>一般都是将文件的私有数据private_data 指向设备结构体,read(),write(),ioctl(),llseek()等函数通过private_data访问设备结构体。

2>除了在globalmem_open()函数中通过filp->private_data = globalmem_devp将设备结构体指针赋值给文件私有数据指

针并在

globalmem_write(),globalmem_read(),globalmem_llseek()和

globalmem_ioctl()函数中通过struct globalmem_dev *dev = filp->private_data语句获得设备结构体指针并使用该指针操作设备结构体。

3>如果globalmem不只包括一个设备,而是同时包括两个或两个以上的设备,采用private_data的优势就会显示出来。

4>在对globalmem_read(),globalmem_write(),globalmem_ioctl()等重要函数及globalmem_fops结构体等数据结构体进行任何修改的前提下,只是简单地修改globalmem_init(),globalmem_exit()和globalmem_open()就可以轻松地让globalmem驱动中包含两个同样的设备(次设备好分别为0和1)

4.支持两个globalmem设备的globalmem驱动

(1)文件打开函数

int globalmem_open(struct inode *inode,struct file *file)

{

       //将设备结构体指针赋值给文件私有数据指针

       struct globalmem_dev *dev;

       dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);

       filp->private_data = dev;

       return 0;

}

1>container_of的作用是通过结构体成员的指针找到对应结构体的指针,这个技巧在Linux内核编程中十分常用。在container_of(inode->i_cdev,struct globalmem_dev,cdev)语句中,传给container_of()的第一个参数是结构体成员的指针,第二个参数为整个结构体的类型,第三个参数为传入的第一个参数即结构体成员的类型,container_of()返回值为整个结构体的指针。

(2)设备驱动模块加载函数

int globalmem_init(void)

{

       int result;

       dev_t devno = MKDEV(globalmem_major,0);

      

       //申请设备号

       if(globalmem_major)

              result = register_chrdev_region(devno,2,"globalmem");

       else//动态申请设备号

           {

              result = alloc_chrdev_region(&devno,0,2,"globalmem");

              globalmem_major = MAJOR(devno);

           }

       if(result < 0)

              return result;

       //动态申请两个设备结构体的内存

    globalmem_devp=kmalloc(2*sizeof(struct                                                 globalmem_dev),GFP,KERNEL);

       if(!globalmem_devp) //申请失败

       {

              result = - ENOMEM;

              goto fail_malloc;  

       }

       memset(globalmem_devp,0,2*sizeof(struct globalmem_dev));

      

       globalmem_setup_cdev(&globalmem_devp[0],0);

       globalmem_setup_cdev(&globalmem_devp[1],1);

       return 0;

      

       fail_malloc: unregister_chrdev_region(devno, 2);

        return result;

      

}

(3)模块卸载函数

void globalmem_exit(void)

{

       cdev_del(&(globalmem_devp[0].cdev));

        cdev_del(&(globalmem_devp[1].cdev));

       kfree(globalmem_devp);//释放设备结构体内存

       unregister_chrdev_region(MKDEV(globalmem_major,0),2);//释放设备号

}

1>在支持两个globalmem设备的驱动,在加载模块后需创建两个设备节点:

/dev/globalmem0对应主设备号globalmem_major,次设备号0,/dev/globalmem1对应主设备号globalmem_major,次设备号1。分别读写/dev/globalmem0和/dev/globalmem1,发现都可以读写到正确的对应设备。

四.globalmem驱动在用户空间的验证

方法一

1.编译globalmem驱动后,得到globalmem.ko文件。运行"insmod globalmem.ko" 命令加载模块,通过"lsmod"命令,发现globalmem模块被加载。在通过“cat/proc/devices”命令查看。

2.接下来,通过"sudo mknod /dev/globalmem c 245 0"命令创建“/dev/gbobalmem”设备节点,并通过“echo hello world>/dev/globalmem”命令和“cat /dev/globalmem”命令分别验证设备的写和读。

3.cd /dev 里可以查看建立的设备节点

如果权限不够用sudo chmod 777 globalmem改权限

方法二

编写应用层程序进行验证

1.头文件

#include<stdio.h>

#include<fcntl.h>

2.

int main()

{

         int myfile;

          char buffer[100];

          int retval;

           myfile = open("/dev/global",O_RDWR);

          if(myfile < 0){

 

                          printf("Open failed/n");

                 }

         write(myfile,"hello,tiger",sizeof("hello,tiger"));

         close(myfile);

         myfile = open("/dev/global",O_RDWR);

         if(myfile < 0){

                         printf("Open failed/n");

         }

 

         retval = read(myfile,buffer,100);

         buffer[retval] = 0;

         printf("Response:%s/n",buffer);

         close(myfile);

 

}

 

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 八个月宝宝不吃辅食怎么办 六个月宝宝不吃米粉怎么办 4个月婴儿积食怎么办 黑色的衣服泛红怎么办 不听劝的人该怎么办 牛仔裙子腰大了怎么办 天猫店铺降权怎么办 淘宝做文胸的标题要怎么办 秋衣弹力衣服松了怎么办 假离婚变成真的怎么办 百家利钱不到账怎么办 皮鞋撑大了怎么办变小 鞋穿着走路掉跟怎么办 浅口帆布鞋大了怎么办 布鞋后鞋跟老掉怎么办 穿坡跟凉鞋容易打滑摔倒怎么办 运动鞋烂了个口怎么办 运动鞋的网烂了怎么办 运动鞋鞋一个一个高一个低怎么办 淘宝预售水果不发货怎么办 淘宝评价忘记晒图了怎么办 参加水果展没有实物怎么办 没做过运营面试怎么办 苹果5s16g内存满了怎么办 618天猫有活动淘宝没有怎么办 天猫国际买到假货怎么办 天猫恶意差评怎么办 天猫删除差评被扣分了怎么办 天猫收到差评怎么办 天猫给差评骚扰怎么办 天猫客户差评怎么办 天猫没法给差评怎么办 天猫上限购一件怎么办 拍下商品不发货怎么办 天猫红包过期了怎么办 买天猫店被中介骗了钱怎么办 速卖通假货纠纷怎么办 天猫新开店被恶意拍下怎么办 新开的天猫店没有生意怎么办 银行的支票丢了怎么办 天猫积分用光了怎么办