前言

这篇文章是通过对一个简单字符设备驱动的操作来解释,用户态的读写操作是怎么映射到具体设备的。

因为针对不同版本的linux内核,驱动的接口函数一直有变化,这贴出我测试的系统信息:

root@ubuntu:~/share/dev/cdev-2# cat /etc/os-release |grep -i ver
VERSION="16.04.5 LTS (Xenial Xerus)"
VERSION_ID="16.04"
VERSION_CODENAME=xenial
root@ubuntu:~/share/dev/cdev-2#
root@ubuntu:~/share/dev/cdev-2# uname -r
4.15.0-33-generic

字符驱动

这里给出了一个不怎么标准的驱动,定义了一个结构体 struct dev,其中buffer成员模拟驱动的寄存器。由wr,rd作为读写指针,len作为缓存buffer的长度。具体步骤如下:

1. 定义 init 函数,exit函数,这是在 insmod,rmmod时候调用的。

2. 定义驱动打开函数open,这是在用户态打开设备时候调用的。

3. 定义release函数,这是在用户态关闭设备时候用到的。

4. 定义read,write,poll函数,并挂接到 file_operations结构体中,所有用户态的read,write,poll都会最终调到这些函数。

chardev.c

/*
参考:深入浅出linux设备驱动开发
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/poll.h> #define MAXNUM 100
#define MAJOR_NUM 400 //主设备号 ,没有被使用 struct dev{
struct cdev devm; //字符设备
struct semaphore sem;
int flag;
poll_table* table;
wait_queue_head_t outq;//等待队列,实现阻塞操作
char buffer[MAXNUM+1]; //字符缓冲区
char *rd,*wr,*end; //读,写,尾指针
}globalvar;
static struct class *my_class;
int major=MAJOR_NUM; static ssize_t globalvar_read(struct file *,char *,size_t ,loff_t *);
static ssize_t globalvar_write(struct file *,const char *,size_t ,loff_t *);
static int globalvar_open(struct inode *inode,struct file *filp);
static int globalvar_release(struct inode *inode,struct file *filp);
static unsigned int globalvar_poll(struct file* filp, poll_table* wait);
/*
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
设备"gobalvar"的基本入口点结构变量gobalvar_fops
*/
struct file_operations globalvar_fops =
{
/*
标记化的初始化格式这种格式允许用名字对这类结构的字段进行初始化,这就避免了因数据结构发生变化而带来的麻烦。
这种标记化的初始化处理并不是标准 C 的规范,而是对 GUN 编译器的一种(有用的)特殊扩展
*/
//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
.read=globalvar_read,
//发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
.write=globalvar_write,
//尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
.open=globalvar_open,
//当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。
.release=globalvar_release,
.poll = globalvar_poll,
};
//内核模块的初始化
static int globalvar_init(void)
{
int result = 0;
int err = 0;
printk("%d\n\n", __LINE__);
dev_t dev = MKDEV(major, 0);
if(major)
{
//静态申请设备编号
result = register_chrdev_region(dev, 1, "charmem1");
}
else
{
//动态分配设备号
result = alloc_chrdev_region(&dev, 0, 1, "charmem1");
major = MAJOR(dev);
}
printk("%d\n\n", __LINE__);
if(result < 0)
{
printk("request or allo devm failed.\n");
return result;
} //注册字符设备驱动,设备号和file_operations结构体进行绑定
cdev_init(&globalvar.devm, &globalvar_fops); printk("%d\n\n", __LINE__);
globalvar.devm.owner = THIS_MODULE;
err = cdev_add(&globalvar.devm, dev, 1);
if(err)
printk(KERN_INFO "Error %d adding char_mem device", err);
else
{
printk("globalvar register success\n");
sema_init(&globalvar.sem,1); //初始化信号量
init_waitqueue_head(&globalvar.outq); //初始化等待队列
globalvar.rd = globalvar.buffer; //读指针
globalvar.wr = globalvar.buffer; //写指针
globalvar.end = globalvar.buffer + MAXNUM;//缓冲区尾指针
globalvar.flag = 0; // 阻塞唤醒标志置 0
printk("%d\n\n", __LINE__);
}
/*
定义在/include/linux/device.h
创建class并将class注册到内核中,返回值为class结构指针
在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
省去了利用mknod命令手动创建设备节点
*/
my_class = class_create(THIS_MODULE, "chardev1");
device_create(my_class, NULL, dev, NULL, "chardev1");
printk("%d\n\n", __LINE__);
return 0;
} static int globalvar_open(struct inode *inode,struct file *filp)
{
try_module_get(THIS_MODULE);//模块计数加一
printk("This chrdev is in open\n");
return(0);
} static int globalvar_release(struct inode *inode,struct file *filp)
{
module_put(THIS_MODULE); //模块计数减一
printk("This chrdev is in release\n");
return(0);
}
static void globalvar_exit(void)
{
device_destroy(my_class, MKDEV(major, 0));
class_destroy(my_class);
cdev_del(&globalvar.devm);
/*
参数列表包括要释放的主设备号和相应的设备名。
参数中的这个设备名会被内核用来和主设备号参数所对应的已注册设备名进行比较,如果不同,则返回 -EINVAL。
如果主设备号超出了所允许的范围,则内核同样返回 -EINVAL。
*/
unregister_chrdev_region(MKDEV(major, 0), 1);//注销设备
} static ssize_t globalvar_read(struct file *filp,char *buf,size_t len,loff_t *off)
{
if(wait_event_interruptible(globalvar.outq, globalvar.flag!=0)) //不可读时 阻塞读进程
{
return -ERESTARTSYS;
} if(down_interruptible(&globalvar.sem)) //P 操作
{
return -ERESTARTSYS;
}
globalvar.flag = 0;
printk("into the read function\n");
printk("the rd is %c\n",*globalvar.rd); //读指针
if(globalvar.rd < globalvar.wr)
len = min(len,(size_t)(globalvar.wr - globalvar.rd)); //更新读写长度
else
len = min(len,(size_t)(globalvar.end - globalvar.rd));
printk("the len is %lu\n",len); if(copy_to_user(buf,globalvar.rd,len))
{
printk(KERN_ALERT"copy failed\n");
/*
up递增信号量的值,并唤醒所有正在等待信号量转为可用状态的进程。
必须小心使用信号量。被信号量保护的数据必须是定义清晰的,并且存取这些数据的所有代码都必须首先获得信号量。
*/
up(&globalvar.sem);
return -EFAULT;
}
printk("the read buffer is %s\n",globalvar.buffer);
globalvar.rd = globalvar.rd + len;
if(globalvar.rd == globalvar.end)
globalvar.rd = globalvar.buffer; //字符缓冲区循环
up(&globalvar.sem); //V 操作
return len;
}
static ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t *off)
{
if(down_interruptible(&globalvar.sem)) //P 操作
{
return -ERESTARTSYS;
}
if(globalvar.rd <= globalvar.wr)
len = min(len,(size_t)(globalvar.end - globalvar.wr));
else
len = min(len,(size_t)(globalvar.rd-globalvar.wr-1));
printk("the write len is %lu\n",len);
if(copy_from_user(globalvar.wr,buf,len))
{
up(&globalvar.sem); //V 操作
return -EFAULT;
}
printk("the write buffer is %s\n",globalvar.buffer);
printk("the len of buffer is %lu\n",strlen(globalvar.buffer));
globalvar.wr = globalvar.wr + len;
if(globalvar.wr == globalvar.end)
globalvar.wr = globalvar.buffer; //循环
up(&globalvar.sem);
//V 操作
globalvar.flag=1; //条件成立,可以唤醒读进程
wake_up_interruptible(&globalvar.outq); //唤醒读进程
return len;
} static unsigned int globalvar_poll(struct file* filp, poll_table* wait)
{
unsigned int mask = 0;
poll_wait(filp, &globalvar.outq, wait);
if (globalvar.wr != globalvar.rd)
{
mask |= POLLIN|POLLRDNORM;
printk("globalvar_poll raise readable\n");
return mask;
}
else
{
mask |= POLLOUT|POLLWRNORM;
printk("globalvar_poll raise writeable\n");
return mask;
}
return 0;
} module_init(globalvar_init);
module_exit(globalvar_exit);
MODULE_LICENSE("GPL");

对应的Makefile:

obj-m := cdev.o

PWD := $(shell pwd)
KDIR := /lib/modules/$(shell uname -r)/build default: clean
$(MAKE) -C $(KDIR) M=$(PWD) CC=gcc-5 modules clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order .cache.mk

编译:

root@ubuntu:~/share/dev/cdev-2# make
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order .cache.mk
make -C /lib/modules/4.15.0-33-generic/build M=/mnt/hgfs/share/dev/cdev-2 CC=gcc-5 modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-33-generic'
CC [M] /mnt/hgfs/share/dev/cdev-2/cdev.o
/mnt/hgfs/share/dev/cdev-2/cdev.c: In function ‘globalvar_init’:
/mnt/hgfs/share/dev/cdev-2/cdev.c:64:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
dev_t dev = MKDEV(major, 0);
^
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/hgfs/share/dev/cdev-2/cdev.mod.o
LD [M] /mnt/hgfs/share/dev/cdev-2/cdev.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-33-generic'
root@ubuntu:~/share/dev/cdev-2# insmod cdev.ko

具体的 log 这里没有给出来但是会在 /var/log/kern.log 记录:

root@ubuntu:~/share/dev/cdev-2# tail -f /var/log/kern.log

Sep 12 16:45:25 ubuntu kernel: [48365.325205]

Sep 12 16:45:25 ubuntu kernel: [48365.325213] globalvar register success

Sep 12 16:45:25 ubuntu kernel: [48365.329010]

用户态函数

用户态 select 机制

reader-writer.c

#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/time.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h> int main()
{
int fd;
fd_set rfd, wfd;
struct timeval timeout = {3,0};
char msg[101];
fd= open("/dev/chardev1",O_RDWR,S_IRUSR|S_IWUSR);
if(fd!=-1)
{
while(1)
{
bzero(msg, 100);
FD_ZERO(&rfd);
FD_SET(fd, &rfd);
FD_ZERO(&wfd);
FD_SET(fd, &wfd);
switch(select(fd+1, &rfd, &wfd, NULL, &timeout))
{
case -1:
printf("error ...\n");
exit(1);
case 0:
printf("timeout\n");
break;
default:
if(FD_ISSET(fd, &wfd))
{
printf("write...\n");
scanf("%s",msg);
write(fd,msg,strlen(msg));
}
if(FD_ISSET(fd, &rfd))
{
printf("read..\n");
read(fd,msg,100);
printf("%s\n",msg);
}
break;
}
}
}
else
{
printf("device open failure,%d\n",fd);
}
close(fd);
return 0;
}

用户态简单读写机制

一下是简单地read,write操作。

writer.c

#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd;
char msg[100];
fd= open("/dev/chardev0",O_RDWR,S_IRUSR|S_IWUSR);
if(fd!=-1)
{
while(1)
{
printf("Please input the globar:\n");
scanf("%s",msg);
write(fd,msg,strlen(msg));
if(strcmp(msg,"quit")==0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure\n");
}
return 0;
}

reader.c

#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd,i;
char msg[101];
fd= open("/dev/chardev0",O_RDWR,S_IRUSR|S_IWUSR);
if(fd!=-1)
{
while(1)
{
for(i=0;i<101;i++)
msg[i]='\0';
read(fd,msg,100);
printf("%s\n",msg);
if(strcmp(msg,"quit")==0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure,%d\n",fd);
}
return 0;
}

测试结论

这里不贴测试图,可自行复制代码,编译测试。

前面利用 poll 机制不会有超时事件,这是因为我们测试是用驱动层面数组模拟的。

当读写指针不等表示可写,否则为可读,所以不存在其他情况,poll每次总能获得可读可写的返回。

linux 字符驱动框架(用户态的read,write,poll是怎么操作驱动的)的更多相关文章

  1. linuxok6410的I2C驱动分析---用户态驱动

    3  i2c-dev 3.1 概述 之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动.不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件 ...

  2. Linux字符界面下用户账户的设置

    在Linux系统字符界面下创建.修改以及删除用户账户主要使用useradd,usermod和userdel这3个命令. 一.创建用户账户 创建用户账户就是在系统中创建一个新账户,然后为新账户分配用户U ...

  3. Linux操作系统学习_用户态与内核态之切换过程

    因为操作系统的很多操作会消耗系统的物理资源,例如创建一个新进程时,要做很多底层的细致工作,如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录.页表等,这些操作显然不能随便让任何程序都可以做,于是就产 ...

  4. 聊聊Linux用户态驱动设计

    序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都有各自的缺点.内核态驱动的问题是: ...

  5. Linux用户态驱动设计

    聊聊Linux用户态驱动设计   序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都 ...

  6. Linux I2C驱动--用户态驱动简单示例

    1. Linux内核支持I2C通用设备驱动(用户态驱动:由应用层实现对硬件的控制可以称之为用户态驱动),实现文件位于drivers/i2c/i2c-dev.c,设备文件为/dev/i2c-0 2. I ...

  7. Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用

    深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...

  8. Linux块设备驱动(二) _MTD驱动及其用户空间编程

    MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...

  9. [中英对照]Device Drivers in User Space: A Case for Network Device Driver | 用户态设备驱动: 以网卡驱动为例

    前文初步介绍了Linux用户态设备驱动,本文将介绍一个典型的案例.Again, 如对Linux用户态设备驱动程序开发感兴趣,请阅读本文,否则请飘过. Device Drivers in User Sp ...

随机推荐

  1. 旧 WCF 项目迁移到 asp.net core + gRPC 的尝试

    一个月前,公司的运行WCF的windows服务器down掉了,由于 AWS 没有通知,没有能第一时间发现问题. 所以,客户提出将WCF服务由C#改为JAVA,在Linux上面运行:一方面,AWS对Li ...

  2. nodejs版本DESede/CBC/PKCS5Padding算法封装(3des)

    最近对接了一个第三方支付项目,用的加密算法是根本没听过的:DESede/CBC/PKCS5Padding 这个算法真的是坑爹了,网上搜索了一堆只有java版本是正常的,nodejs版本的各种问题,我了 ...

  3. offer收割机也有方法论

    秋招的战火就像这夏天的温度一样炙热,陆陆续续很多学弟学妹问我秋招的注意事项,作为温暖型大叔的我此刻必须出场了. 看仔细了,接下来龙叔就把这offer收割机的秘密都告诉你们. 如果你还没点关注的话,记得 ...

  4. 逃离CSDN -慕舲的黑夜-第三期

    来时,是朋友推荐查资料,后来看到CSDN的UI,好华丽高大上,也读了CSDN首页推荐的一些文章,加入CSDN. 可是后来随着博客园,蓝奏云,w3c菜鸟教程,等平台的出现,CSDN越来越令人心寒

  5. css3新属性position: sticky 一分钟实现 导航栏悬停功能

    css3新属性position: sticky 一分钟实现 导航栏悬停功能 前言 正文 前言 想必很多前端小伙伴经常会在开发中遇到这样一个需求,就是在下划时,导航栏悬停在屏幕最上方,例如咱们的csdn ...

  6. servlet的生命周期和工作原理介绍

    一.servlet生命周期 Servlet生命周期分为三个阶段: 1)初始化阶段: 调用init()方法 2)响应客户请求阶段:调用service()方法 3)终止阶段:调用destroy()方法 T ...

  7. linux下显示dd命令的进度

    sudo dd if=/dev/zero of=/tmp/zero.img status=progresslinux下显示dd命令的进度:dd if=/dev/zero of=/tmp/zero.im ...

  8. Linux 常用软件清单

    Linux 常用软件清单 下面是Linux环境的一些软件(有些只是关键字,直接搜素即可): arch 系列的是 pacman -Ss <关键字> debian 系列的是 apt searc ...

  9. DBeaver链接kerberos安全认证的Phoenix集群

    DBeaver链接kerberos安全认证的Phoenix集群 最近公司的CDH集群,启动了kerberos安全认证,所有的用户验证全部需要依赖kerberos来进行.之前的裸奔集群,总算有了一些安全 ...

  10. 新网站seo如何优化

    http://www.wocaoseo.com/thread-189-1-1.html      做seo优化也有一段时间了,有做过自己的博客,自己的论坛,也有做过公司企业的网站,有同时一个人完成8个 ...