第6章 字符设备驱动之globalmem(全局内存)虚拟设备实例(单个字符设备)

本节概述

在globalmem(全局内存)字符设备驱动中会分配一片大小为GLOBALMEM_SIZE(4KB)的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过Linux系统调用获取或设置这片内存的内容。

1、ioctl()命令

Linux内核推荐采用一套统一的ioctl()命令生成方式,Linux建议以如图6.2所示的方式定义ioctl()的命令。


命令码的设备类型字段为一个“幻数”,可以是0~0xff的值,内核中的Documentation/ioctl/ioctl-number.txt给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义“幻数”的时候要避免与其冲突。

命令码的序列号也是8位宽。

命令码的方向字段为2位,该字段表示数据传送的方向,可能的值是_IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)和_IOC_READ|_IOC_WRITE(双向)。数据传送的方向是从应用程序的角度来看的。

命令码的数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13或者14位。

内核还定义了_IO()、_IOR()、_IOW()和_IOWR()这4个宏来辅助生成命令,这4个宏的通用定义如代码清单6.15所示。

代码清单6.15 _IO()、_IOR()、_IOW()和_IOWR()宏定义在<asm-generic/ioctl.h>

/* _IO 、 _IOR 等使用的 _IOC 宏 */

#define _IOC(dir,type,nr,size) \
(((dir)  << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr)   << _IOC_NRSHIFT) | \

((size) << _IOC_SIZESHIFT))

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

由此可见,这几个宏的作用是根据传入的type(设备类型字段)、nr(序列号字段)、size(数据长度字段)和宏名隐含的方向字段移位组合生成命令码。

由于globalmem的MEM_CLEAR命令不涉及数据传输,所以它可以定义为:

#define GLOBALMEM_MAGIC 'g'

#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)

2、预定义命令

内核中预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会作为预定义命令被内核处理而不是被设备驱动处理,下面列举一些常用的预定义命令。

FIOCLEX:File IOctl Close on Exec,对文件设置专用标志,通知内核当exec()系统调用发生时自动关闭打开的文件。

FIONCLEX:即File IOctl Not Close on Exec,与FIOCLEX标志相反,清除由FIOCLEX命令设置的标志。

FIOQSIZE:获得一个文件或者目录的大小,当用于设备文件时,返回一个ENOTTY错误。

FIONBIO:即File IOctl Non-Blocking I/O,这个调用修改在filp->f_flags中的O_NONBLOCK标志。

FIOCLEX、FIONCLEX、FIOQSIZE和FIONBIO这些宏定义在内核的include/asm-generic/ioctls.h文件中。

例子:字符设备驱动之globalmem(全局内存)虚拟设备实例

3、驱动源代码头文件

global_mem.h

#ifndef __GLOBAL_MEM_H
#define __GLOBAL_MEM_H

#include <linux/cdev.h>

#define DRIVER_AUTHOR "xz@vi-chip.com.cn"
#define DRIVER_DESC   "A sample driver" 

#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define DEV_NAME "globalmem"
#define GLOBALMEM_MAJOR 230 /* 主设备号 */

/**
*
* 定义全局内存字符设备的结构体:
* 借用面向对象程序设计中“封装”思想,体现良好的编程习惯。
*
*/
struct globalmem_dev {
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
};

#endif /* __GLOBAL_MEM_H */

4、驱动源代码global_mem.c

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

/* include local head files */
#include "global_mem.h"

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO); /* mode:S_IRUGO */

struct globalmem_dev *globalmem_devp = NULL;

/**
*
* 读写函数
* 让设备结构体globalmem_dev的mem[]数组与用户空间交互数据,
* 并随着访问的字节数变更更新文件读写偏移位置。
*/
// globalmem设备驱动的读函数,从设备中读取数据
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos; // 要读的位置相对于文件开头的偏移,如果该偏移大于或等于GLOBALMEM_SIZE,表示已经到达文件末尾,所以返回0(EOF)。
unsigned int count = size; // 要读取的字节数
int ret = 0;
struct globalmem_dev *dev = filp->private_data; // 使用文件的私有数据访问设备结构体

if (p >= GLOBALMEM_SIZE)
return 0; // 表示已经到达文件末尾,所以返回0(EOF)
if (count > (GLOBALMEM_SIZE - p))
count = GLOBALMEM_SIZE - p;

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

return ret; // 实际读取的字节数
}

// globalmem设备驱动的写函数,向设备发送数据
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;

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

return ret;
}

// globalmem设备驱动的seek函数
// seek函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),假设globalmem支持从文件开头和当前位置的相对偏移。
// 在定位的时候,应该检查用户请求的合法性,若不合法,函数返回-EINVAL,合法时更新文件的当前位置并返回该位置。
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 从文件开头位置 seek */
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: /* 从文件当前位置开始 seek */
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;
}

// ioctl函数
// globalmem设备驱动的ioctl函数接受MEM_CLEAR命令,这个命令会将全局内存的有效数据长度清0,对于设备不支持的命令,ioctl函数应该返回-EINVAL。
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:
memset(dev->mem, 0, GLOBALMEM_SIZE); // 清空内存
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}

return 0;
}

static int globalmem_open(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalmem_open\n");
filep->private_data = globalmem_devp;// 设置私有数据,将文件的私有数据private_data指向设备结构体的实例,体现Linux的面向对象的设计思想

return 0;
}

static int globalmem_release(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "globalmem_release\n");
return 0;
}

// globalmem设备驱动的文件操作结构体
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,
};
 
void globalmem_setup_cdev(struct globalmem_dev *dev, int minor)
{
int err, devno = MKDEV(globalmem_major, minor);

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, minor);
}

static int __init global_mem_init(void)
{
int ret;

dev_t devno = MKDEV(globalmem_major, 0); /* 主设备号、次设备号合并为设备号 */
if (globalmem_major)
ret = register_chrdev_region(devno, 1, DEV_NAME); // 静态分配设备号
else {
ret = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); // 动态分配设备号
globalmem_major = MAJOR(devno); // 提取主设备号
}
if (ret < 0)
return ret;

globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL); /* 分配内存并且清空内存 */
if (!globalmem_devp) {
ret = -ENOMEM;
goto fail_kmalloc;
}

globalmem_setup_cdev(globalmem_devp, 0); // 注册字符设备
    printk(KERN_INFO "----global_mem_init----\n");
    return 0; // 表示成功

fail_kmalloc:
unregister_chrdev_region(devno, 1); // 注销分配设备号
return ret;
}

static void __exit global_mem_exit(void)
{
cdev_del(&globalmem_devp->cdev); // 注销字符设备
kfree(globalmem_devp); // 释放分配的内存空间
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);// 注销分配设备号
    printk(KERN_INFO "----global_mem_exit----\n");
}

module_init(global_mem_init);
module_exit(global_mem_exit);

MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_ALIAS(DRIVER_DESC);

5、Makefile

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:                               
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:                                             
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
    obj-m := global_mem.o

endif

6、编写测试文件

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <string.h>  
#define MEM_CLEAR 0x1  


#define DEV_NAME "/dev/globalmem"

int main(void)  
{  
    int fd,ret;  
    char* name="Hello Linux";  
    char test[100];  
    fd= open(DEV_NAME,O_RDWR);  
    if(fd<0)  
    {  
        perror("open error"); 
return -1;
    }  
    write(fd,name,strlen(name));  
    close(fd);  
    fd=open("/dev/globalmem",O_RDWR);  
    ret=read(fd,test,20);  
    test[ret]='\0';  
    ioctl(fd,MEM_CLEAR,1);  
    close(fd);  
    printf("%s\n",test);  
  
    return 0;  
}    

7、验证

1)sudo insmod global_mem.ko

2)lsmod 

Module                  Size  Used by
global_mem             12934  0 

autofs4                37024  1 

global_mem模块已被加载j

3)cat /proc/devices

Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 21 sg
 29 fb
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
226 drm
230 globalmem

结论:主设备号为230的“globalmem”字符设备驱动

4、ls -l /dev

crw-rw----  1 root video    10, 175 May 19 19:33 agpgart
crw------T  1 root root     10, 235 May 19 19:34 autofs
drwxr-xr-x  2 root root         680 May 20  2018 block
drwxr-xr-x  2 root root          60 May 20  2018 bsg
crw------T  1 root root     10, 234 May 19 19:33 btrfs-control
drwxr-xr-x  3 root root          60 May 20  2018 bus
drwxr-xr-x  2 root root        3920 May 19 19:34 char
crw-------  1 root root      5,   1 May 19 19:34 console
lrwxrwxrwx  1 root root          11 May 19 19:33 core -> /proc/kcore
drwxr-xr-x  2 root root          60 May 19 19:33 cpu
crw-------  1 root root     10,  60 May 19 19:33 cpu_dma_latency
drwxr-xr-x  5 root root         100 May 20  2018 disk
drwxr-xr-x  2 root root          80 May 20  2018 dri
crw-------  1 root root     10,  61 May 19 19:33 ecryptfs
crw-rw----  1 root video    29,   0 May 19 19:33 fb0
lrwxrwxrwx  1 root root          13 May 19 19:33 fd -> /proc/self/fd
crw-rw-rw-  1 root root      1,   7 May 19 19:33 full
crw-rw-rwT  1 root fuse     10, 229 May 19 19:33 fuse

crw-rw-rw-  1 root root    230,   0 May 19 19:54 globalmem

结论:创建字符设备globalmem,主设备号20、次设备号0。

5、向设备驱动写入数据

echo "hello world" > /dev/globalmem

6、从设备中读取数据

cat /dev/globalmem

hello world

7、如果启用sysfs文件系统,将发现多出了/sys/module/global_mem(模块名称)目录,该目录下的树形结构为:

/sys/module/global_mem
├── holders
├── initstate
├── notes
├── parameters
│   └── globalmem_major
├── refcnt
├── sections
│   ├── __mcount_loc
│   └── __param
├── srcversion
├── uevent

└── version

结论:refcnt记录了global_mem模块的引用计数,sections下包含的几个文件则给出了global_mem所包含的BSS、数据段和代码段等的地址及其他信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值