linux设备驱动学习(二)——字符设备编写及测试

一、字符设备体结构介绍

1.字符设备作为linux内核三大驱动设备之一,主要完成字节的读写操作,常见的应用有鼠标、键盘等,结构体形式如下所示:

struct cdev{

struct kobject kobj;

struct module *owner;//所说模块

struct file_operations *ops;//字符设备操作方法

struct list_head list;  

dev_t dev;     //设备

unsigned int count;

}

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列
宏可以从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)


而使用下列宏则可以通过主设备号和次设备号生成dev_t
MKDEV(int major, int minor)
2.字符设备结构体操作方法,Linux内核提供了一组函数以用于操作cdev结构体:
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接
cdev_alloc()函数用于动态申请一个cdev内存
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

3.分配设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或
alloc_chrdev_region()函数向系统申请设备号,这两个函数的原型为:

int register_chrdev_region(dev_t from, unsigned count, const char *name); //已知起始设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//位置起始设备号

4.file_operations结构体
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations结构体目前已经比较庞大:
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。它与用户空间应用程序中的ssize_t read(int fd,void*buf,size_t count)和size_t fread(void*ptr,size_t size,size_t nmemb,FILE*stream)对应。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。它与用户空间应用程序中的ssize_t write(int fd,constvoid*buf,size_t count)和size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream)对应。
read()和write()如果返回0,则暗end-of-file(EOF)。
unlocked_ioctl()提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。它与用户空间应用程序调用的int fcntl(int fd,int cmd,.../*arg*/)和intioctl(int d,int request,...)对应。
mmap()函数将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义,帧缓冲被映射到用户空间后,应用程序可以直接访问它而无须在内核和应用间进行内存复制。它与用户空间应用程序中的void*mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset)函数对应。


5.linux字符设备组成
(1)字符设备驱动模块加载与卸载函数
static int __init globalmem_init(void) 
static void __exit globalmem_exit(void)
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注销。

扫描二维码关注公众号,回复: 1602419 查看本文章

二、字符结构体编码实现

在内核代码中.../drivers/   新建globalmem文件夹,在此目录下新建globalmem.c 和相应的Makefile文件

1.globalmem.c文件

#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

static int globalmem_major = GLOBALMEM_MAJOR; //定义主设备号
module_param(globalmem_major, int, S_IRUGO);//模块传参

struct globalmem_dev {   //定义globalmen_dev结构体
 struct cdev cdev;//字符结构体
 unsigned char mem[GLOBALMEM_SIZE];//使用内存
};

struct globalmem_dev *globalmem_devp;//申明globalmem结构对象


//globalmem设备驱动的读函数
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;
 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;
}
//寻址函数
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;
}

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;
}
//open函数
static int globalmem_open(struct inode *inode, struct file *filp)
{
 filp->private_data = globalmem_devp;
 return 0;
}

//release函数
static int globalmem_release(struct inode *inode, struct file *filp)
{
 return 0;
}

/*定义字符结构体方法*/
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);//获取设备结构体dev_t


 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;
 dev_t devno = MKDEV(globalmem_major, 0);//获取字符设备结构体
 if (globalmem_major)
 ret = register_chrdev_region(devno, 1, "globalmem");//注册此cdev设备
 else {
 ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");//申请字符设备cdev空间
 globalmem_major = MAJOR(devno);//获取主设备号
 }
 if (ret < 0)
 return ret;
 globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);//分配globalmem结构体内存
 if (!globalmem_devp) {
 ret = -ENOMEM;
 goto fail_malloc;    //分配失败择跳转

 }

//主次设备的不同
 globalmem_setup_cdev(globalmem_devp, 0);
 return 0;
 fail_malloc:
 unregister_chrdev_region(devno, 1);
 return ret;
}
module_init(globalmem_init);


//模块卸载函数
static void __exit globalmem_exit(void)
{
 cdev_del(&globalmem_devp->cdev);
 kfree(globalmem_devp);
 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <[email protected]>");

MODULE_LICENSE("GPL v2");


2.Makefile文件编写

KVERS = $(shell uname -r)
# Kernel modules
obj-m += globalmem.o

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:

make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean


3.字符设备验证

(1)在globalmem目录下make


(2)管理员身份插入模块


(3)输入字符到此字符设备中

创建设备节点:mknod /dev/globalmem c 230 0   //230 0 为你创建设备的主设备与次设备号

写入字符串:echo "hello world!">/dev/globalmem 

查看输入信息:cat /dev/globalmem


查看读写情况:dmesg -c globalmem


4.编写测试代码验证

建立globalmemTest测试文件,代码如下所示:

#include<fcntl.h>
#include<stdio.h>


int main(void)
{
char s[] = "Linux Programmer!\n";
char buffer[80];
int fd=open("/dev/globalmem",O_RDWR);//打开globalmem设备,fd返回大于2的数则成功,O_RDWR为权限赋予
write(fd,s,sizeof(s));          //将字符串s写入globalmem字符设备中
printf("test write %d %s\n",fd,s );  
        close(fd);  //关闭设备
fd=open("/dev/globalmem",O_RDWR);
read(fd,buffer,sizeof(buffer));   //读取globalmem设备中存储的数据
printf("test read %d %s\n",fd,buffer);  //输出结果显示
return 0;

}

结果展示:


猜你喜欢

转载自blog.csdn.net/kuishao1314aa/article/details/80505657