ioctl---字符设备的控制技术

字符设备的控制

1. 字符设备控制理论

    1.1 作用

          大部分驱动程序除了需要提供读写设备的能力外,还需要具备控制设备的能力。比如:改变波特率

    1.2 应用程序接口

          在用户空间,使用ioctl系统调用来控制设备,原型如下:

          int  ioctl(int fd, unsigned long cmd, ...)

          fd: 要控制的设备文件描述符

          cmd: 发送给设备的控制命令

          ...: 第三个参数是可选的参数,存在与否依赖于控制命令(第二个参数)

    1.3 设备驱动方法

    ioctl---字符设备的控制技术_第1张图片

2. 字符设备控制实现

    2.1 定义命令 

          命令其实质而言就是一个整数,但为了让这个整数具备更好的可读性,我们通常会把这个整数分为几个段,类             型(8位),序号, 参数传送方向,参数长度

          type(类型/幻数):表明这是属于哪个设备的命令

          number(序号):用来区分同一设备的不同命令

          direction: 参数传送的方向,可能的值是_IOC_NONE(没有数据传输),_IOC_READ, _IOC_WRITE(向设备读写           参数)

          size : 参数长度

          Linux系统提供了下面的宏来帮助定义命令:

          * _IO(type, nr) : 不带参数的命令

          *_IOR(type, nr, datatype) : 从设备中读取参数的命令

          *_IOW(type, nr, datatype) : 向设备写入参数的命令

          例如:

                  #define MEM_MAGIC  'm' //定义幻数

                  #define MEM_SET  _IOW(MEM_MAGIC, 0, int)

    2.2 实现设备方法

          unlocked_ioctl函数的实现通常是根据命令执行的一个switch语句。但是,当命令号不能匹配任何一个设备所支           持的命令时, 返回 -EINVAL

          编程模型:

                 switch cmd:

                     case  命令A:

                        //执行A所对应的操作

                case  命令B:

                        //执行B对应的操作

                defaule:

                       return -EINVAL;

实例代码分析来实现ioctl:

这里的memdev.c还是接着上篇的基础上改的

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "memdev.h"


int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);
    
    if (num==0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //无效的次设备号
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/

  /*判断读位置是否有效*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;

  /*读数据到用户空间*/
  if (copy_to_user(buf, register_addr+p, count))
  {
    ret = -EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/*写函数*/
static ssize_t mem_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;
  int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
  
  /*分析和获取有效的写长度*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;
    
  /*从用户空间写入数据*/
  if (copy_from_user(register_addr + p, buf, count))
    ret = -EFAULT;
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
      case SEEK_SET: 
        newpos = offset;
        break;

      case SEEK_CUR: 
        newpos = filp->f_pos + offset;
        break;

      case SEEK_END: 
        newpos = 5*sizeof(int)-1 + offset;
        break;

      default: 
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>5*sizeof(int)))
    	return -EINVAL;
    	
    filp->f_pos = newpos;
    return newpos;

}
//设备控制函数
long mem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{

	switch(cmd)
	{
		case MEM_RESTART: //假如这里传入的是重启的命令
			printk(KERN_WARNING"restart device!\n");//这里通过虚拟设备来模拟
			return 0;
		case MEM_SET: //如果传入的是设置参数命令
			printk(KERN_WARNING"arg is : %d\n", arg);//打印出传入的参数
			return 0;
		default:
			return -EINVAL;//其他的打印错误
			
	}
	
	return 0;
}


/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
  .unlocked_ioctl = mem_ioctl,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  
  /* 注册字符设备 */
  alloc_chrdev_region(&devno, 0, 2, "memdev");
  cdev_add(&cdev, devno, 2);
  //printk(KERN_WARNING"Hello Memdev!\n");
  printk("Hello Memdev!\n");
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  unregister_chrdev_region(devno, 2); /*释放设备号*/
  printk(KERN_WARNING"exit Memdev!\n");
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);
memdev.h

#define MEM_MAGIC 'm' //定义一个幻数,而长度正好和ASC码长度一样为8位,所以这里定义个字符
#define MEM_RESTART _IO(MEM_MAGIC, 0)   //第一个命令是重启的命令,重启的命令不带参数 第一命令这里序号定义成0
#define MEM_SET  _IOW(MEM_MAGIC, 1, int) //设置参数命令 序号为1, 类型为int


测试应用程序mem_ctl.c

#include
#include
#include
#include
#include "memdev.h"

int main()
{
	int fd;
	
	fd = open("/dev/memdev0", O_RDWR);//可读可写打开文件
	
	ioctl(fd, MEM_SET, 115200);//第一个参数fd,第二个是我们要发送的命令,第三个是要传入的参数
	
	ioctl(fd, MEM_RESTART);//重启
	
	close(fd);
}

Makefile

obj-m := memdev.o
KDIR := /home/kernel/linux-ok6410
all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order

这里ioctl函数原型可以查看内核源码Fs.h(/include/linux目录下)文件

ioctl---字符设备的控制技术_第2张图片


ioctl---字符设备的控制技术_第3张图片
然后这次采用静态编译mem_ctl.c文件 arm-linux-gcc -static -o memctl (这里采用静态编译上面博文有说道)

由于挂载了整个rootfs目录,这里编译产生的文件会同步到我的开发板

ioctl---字符设备的控制技术_第4张图片

安装设备驱动模块,然后cat /proc/device 查看设备驱动程序的主设备号

ioctl---字符设备的控制技术_第5张图片

然后创建设备文件,这样我们的应用程序就可以通过这个设备文件来访问设备了(当然这里我们的设备是虚拟出来的字符设备,不过麻雀虽小五脏俱全)

mknod memdev0 c 252 0 (执行该命令)

memdevo是我们给字符设备取的名字, 252是该字符设备的设备号,


这里可以看到/dev目录下就会产生memdev0这个设备文件了,运行mem_clt应用程序

ioctl---字符设备的控制技术_第6张图片

至此ioctl字符设备的控制就OVER了!

你可能感兴趣的:(Linux驱动编程)