(一)Linux驱动开发·字符串设备驱动

本文介绍了Linux驱动开发中的字符串设备驱动,涉及file、inode和cdev等关键结构体,以及file_operations中的主要函数。文章还列举了用于设备注册和管理的基础API,如cdev_init、cdev_add和register_chrdev_region等。此外,提供了一个简单的内存设备驱动示例,展示了如何实现打开、读取、写入、ioctl等操作。
摘要由CSDN通过智能技术生成

(一)Linux驱动开发·字符串设备驱动

一、基础结构体
  1. file结构体
    struct file {
      ...
      struct path f_path;
      struct inode *f_inode;
      const struct file_operations *f_op;
      unsigned int f_flags; /*文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC*/
      fmode_t f_mode; /*文件读写模式*/
      loff_t f_pos /*当前读写位置*/
      void *private_data; /*文件私有数据,一般指向描述设备的结构体如cdev*/
      ...
    } __attribute__{(aligned(4))};
    
  2. inode结构体
    struct inode {
      ...
      umode_t i_mode; /*inode的权限*/
      dev_t i_rdev;   /*记录设备的设备号,高12位为主设备号,低20位为次设备号*/
      struct timespec i_atime; /*inode 最近一次的存取时间*/
      struct timespec i_mtime; /*inode 最近一次的修改时间*/
      struct timespec i_ctime; /*inode 的产生时间*/
      union {
        struct pipe_inode_info *i_pipe;
        struct block_device *i_bdev;/*若是块设备,为其对应的block_device结构体指针*/
        struct cdev *i_cdev;        /*若是字符设备,为其对应的cdev结构体指针*/
      }
      ...
    };
    
  3. cdev 结构体
    struct cdev {
      struct kobject kobj;  /*内嵌kobject对象*/
      struct module *owner; /*所属模块*/
      struct file_operations *ops; /*文件操作结构体*/
      struct list_head list; 
      dev_t dev;             /*设备号*/
      unsigned int count;
    };
    
  4. file_operations
    /**llseek():修改文件当前读写位置,并将新位置放回,出错时返回负数。
     *read():从设备中读取数据,成功时返回读取的字节数,出错时返回一个负数。
     *write():向设备发送数据,成功时返回写入的字节数。
     *unlocked_ioctl():提供设备相关的控制指令的实现,调用成功时返回一个非负值。
     *与用户空间的int fcntl(int fd,int cmd,...)和int ioctl(int d,int request,...)对应
     *poll():用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,
     *用户空间进行select()和poll()系统调用将引起进程的阻塞。
     **/
    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 *);
      int (*open)(struct inode *,struct file *);
      int (*release)(struct inode *,struct file *);
      long (*unlocked_ioctl)(struct file *,unsigned int ,unsigned long);
      long (*compat_ioctl)(struct file *,unsigned int,unsigned long);
      unsigned int (*poll)(struct file *,struct poll_table_struct *);;
      ...
    };
    
二、基础API
API功能说明
void cdev_init(struct cdev*,struct file_operations *)初始化cdev并建立cdev和file_operations的连接
struct cdev *cdev_alloc(void)动态申请一个cdev内存
void cdev_add(struct cdev *,dev_t, unsigned)向系统添加一个cdev完成字符设备的注册
void cdev_del(struct cdev *)从系统中删除一个cdev完成字符设备的注销
int register_chrdev_region(dev_t from, unsigned count, const char *name)向系统注册一个设备号
int alloc_chardev_region(dev_t *dev,unsigned baseminor,unsigned count,const char name)向系统申请并注册一个未被占用的设备号,通过*dev指针返回
void unregister_chrdev_regin(dev_t from, unsigned count)从系统中注销一个设备号
MAJOR(dev_t dev)返回设备号的主设备号
MINOR(dev_t dev)返回设备号的次设备号
MKDEV(int major, int minor)根据主设备号和次设备号生成 dev_t
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count)从用户空间拷贝数据到内核空间
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count)从内核空间拷贝数据到用户空间
access_ok(type,addr,size)判断传入的缓冲区确实属于用户空间
put_user(auto data,auto __user *to)简单类型的数据(char int long)拷贝到用户空间,有做缓冲区检测
get_user(auto data,auto __user *from)简单类型的数据(char int long)拷贝到内核空间,有做缓冲区检测
__put_user(auto data,auto __user *to)与put__user 类似,不做缓冲区检测
__get_user(auto data, auto __user *from)与get_user类似,不做缓冲区检测
三、框架代码
/*
 * a simple char device driver: globalmem with mutex
 *
 * Copyright (C) 2014 Barry Song  (baohua@kernel.org)
 *
 * Licensed under GPLv2 or later.
 * 
 * 驱动功能说明:
 * 该程序是一个虚拟内存设备的驱动程序。每个设备指向一块0x1000大小的内存。该驱动程序支持对该
 * 虚拟设备进行如下操作:
 * 1. open: 打开
 * 2. read: 读取内存中的内容
 * 3. write: 向内存中写入数据
 * 4. seek: 支持seek方法
 * 5. ioctl: 支持ioctl方法,发送MEM_CLEAR可清除内存中的数据
 * 6. release: 释放内存
 * 该驱动支持最多同时操作10个设备
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE  0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230  //主设备号
#define DEVICE_NUM 10

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO); //申明内核参数

struct globalmem_dev {
  struct cdev cdev;
  unsigned char mem[GLOBALMEM_SIZE];
  struct mutex mutex;
};

struct globalmem_dev *globalmem_devp;

static int globalmem_open(struct inode *inode, struct file *filp)
{
  struct globalmem_dev *dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
  filp->private_data = dev; //字符设备的结构体赋值给文件的私有数据
  return 0;
}

int globalmem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd,
          unsigned long arg)
{
  struct globalmem_dev *dev = filp->private_data;

  switch (cmd) {
  case MEM_CLEAR:
    mutex_lock(&dev->mutex);
    memset(dev->mem, 0, GLOBALMEM_SIZE);
    mutex_unlock(&dev->mutex);

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

  default:
    return -EINVAL;
  }

  return 0;
}

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 0;
  if (count > GLOBALMEM_SIZE - p)
    count = GLOBALMEM_SIZE - p;

  mutex_lock(&dev->mutex);
  if (copy_to_user(buf, dev->mem + p, count)) {
    ret = -EFAULT;
  } else {
    *ppos += count;
    ret = count;

    printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
  }
  mutex_unlock(&dev->mutex);

  return ret;
}

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 0;
  if (count > GLOBALMEM_SIZE - p)
    count = GLOBALMEM_SIZE - p;

  mutex_lock(&dev->mutex);

  if (copy_from_user(dev->mem + p, buf, count))
    ret = -EFAULT;
  else {
    *ppos += count;
    ret = count;

    printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
  }
  mutex_unlock(&dev->mutex);

  return ret;
}

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;
    }
    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;
    }
    filp->f_pos += offset;
    ret = filp->f_pos;
    break;
  default:
    ret =  - EINVAL;
    break;
  }
  return ret;
}

static const struct file_operations globalmem_fops = {
  .owner = THIS_MODULE,
  .llseek = globalmem_llseek,
  .read = globalmem_read,
  .write = globalmem_write,
  .unlocked_ioctl = globalmem_ioctl,
  .open = globalmem_open,
  .release = globalmem_release,
};

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
  int err, devno = MKDEV(globalmem_major, index); //构建具体的设备号

  cdev_init(&dev->cdev, &globalmem_fops); //初始化设备
  dev->cdev.owner = THIS_MODULE;
  err = cdev_add(&dev->cdev, devno, 1);  //将设备添加到系统中
  if (err)
    printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}

static int __init globalmem_init(void)
{
  int ret;
  int i;
  dev_t devno = MKDEV(globalmem_major, 0); //构建基础的设备号

  if (globalmem_major)
    ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem"); //申请并注册多个设备号
  else {
    ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalmem");
    globalmem_major = MAJOR(devno);
  }
  if (ret < 0)
    return ret;
  
  // 动态分配连续多个设备的内存
  globalmem_devp = kzalloc(sizeof(struct globalmem_dev)*DEVICE_NUM, GFP_KERNEL);
  if (!globalmem_devp) {
    ret = -ENOMEM;
    goto fail_malloc;
  }
  
  for (i = 0;i<DEVICE_NUM;i++)
  {
    globalmem_setup_cdev(globalmem_devp+i, i);
    mutex_init(&(globalmem_devp+i)->mutex);
  }
  return 0;

 fail_malloc:
  unregister_chrdev_region(devno, DEVICE_NUM);
  return ret;
}
module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
  int i;
  for (i =0;i<DEVICE_NUM;i++)
  {
    cdev_del(&(globalmem_devp+i)->cdev); //从系统中删除设备
  }
  kfree(globalmem_devp); //释放设备的内存
  unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM); //注销所有的设备号
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
MODULE_LICENSE("GPL v2");

测试方法:

# 创建设备节点0
mknod /dev/globalmem0 c 230 0 # 修改为 mknod /dev/globalmem1 c 230 1 可创建设备节点1
echo "hello world" > /dev/globalmem0 
cat /dev/globalmem0  # 输出内存中的内容: hellow world
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值