标题:
(转) ok6410学习笔记(5.阻塞字符驱动)
[打印本页]
作者:
bingchentiao
时间:
2013-12-17 19:25
标题:
(转) ok6410学习笔记(5.阻塞字符驱动)
本节难点:
1.重点在理解阻塞型设备驱动和内核信号量的区别,详细在内核信号量那节有说明。
2.理解本节的几个重要函数,驱动的结构。
本节知识点: 细节知识点:
1.
在判断是否阻塞进程的时候,使用了while的好处是,可以避免wakeup唤醒的进程不是现在你想唤醒的进程,因为wakeup是唤醒等待队列中的一类进程,而不是一个。所以在wakeup的使用中要伴随着 阻塞条件的赋值(可能是全局变量也可能是file->private_data中的值)。在后面的国嵌实验例程中,能常看见一个进程一个等待队列,就是为了防止wakeup和一些中断信号唤醒了你不想唤醒的进程。
2.filp->f_flags与O_NONBLOCK 判断这个文件是否允许阻塞,不允许就不能wait 这个值是在应用层函数open中的flag参数中定义的。
驱动结构:
1.用全局变量或者file->private_data指针中定义
等待队列
和
阻塞条件
2.在module_init中初始化等待队列
3.在读函数中轮询判断阻塞条件,判断文件是否允许阻塞,wait使进程进入等待队列
4.在写函数中改变阻塞条件,wake_up唤醒等待队列中的进程
重点函数:
1.定义等待队列 wait_queue_head_t q
2.初始化等待队列init_waitqueue_head(&q)
3.可以用宏完成以上两步 DECLARE_WAIT_QUEUE_HEAD(q)
4.有条件睡眠wait_event(队列,条件)
这个是不可中断的 不推荐
wait_event_interruptible(队列,条件) wait_event_killable()
5.无条件睡眠sleep_on(q) interruptible_sleep_on(q)
6.唤醒函数wake_up(q) wake_up_interruptible(q)
注意事项:
1.貌似定义阻塞调试的时候不能使用bool类型 我用的gcc是没编译过 改成了int类型 编译通过
本节代码:
memdev.c
[cpp]
view plain
copy
/**************************************************************************
文件名: memdev.c
日期: 2013/05/12 17:21
头文件: memdev.h
功能: 简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
环境: Redhat企业版5 内核版本2.6.18-53.el5
作者: Hao
流程: 1.分配设备号(a.静态申请 b.动态分配)
2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名” b.自动创建)
3.设备注册(a.设备注册分配 b.设备注册初始化 c.设备注册添加)
4.实现file_operation中的函数
5.设备注销(cdev_del)
6.设备号注销
***************************************************************************/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "memdev.h"
MODULE_AUTHOR("Hao");
MODULE_LICENSE("GPL");
static
int
mem_major=MEMDEV_MAJOR; //定义主设备号(定义一个全局变量在read write memdev_exit中都能用 )
module_param(mem_major,
int
, S_IRUGO);//接收模块参数主设备号
struct
mem_dev *mem_devp; //定义mem设备描述结构体指针
struct
cdev c_dev; //设备注册分配 替换了struct cdev *cdev_alloc() 全局变量都可以使用
int
canRead=0;//这个值是进入等待队列的条件 因为是read先运行 所以应该是先让read阻塞
/**************************************************************************
函数名: mem_open
函数功能: 文件打开函数 使private_data指向 字符驱动模块
函数参数: inode存储文件物理信息的结构(包含设备号) file结构
函数返回值: 返回0为正常执行
***************************************************************************/
int
mem_open(
struct
inode *inode,
struct
file *filp)
{
struct
mem_dev *dev;
int
num = MINOR(inode->i_rdev); //获得次设备号
if
(num >= MEMDEV_NR_DEVS)
return
-ENODEV; //判断是否次设备号 不正确
dev = &mem_devp[num];
filp->private_data = dev; /*不是很了解private_data
宋宝华的linux设备驱动开发详解,93页写到私有数据指针private_data在设备驱动中背广泛使用,大多数指向设备驱动自定义用于描述设备的结构体。*/
return
0;
}
/**************************************************************************
函数名: mem_release
函数功能: 文件关闭函数
函数参数: inode存储文件物理信息的结构(包含设备号) file结构
函数返回值: 返回0为正常执行
***************************************************************************/
int
mem_release(
struct
inode *inode,
struct
file *filp)
{
return
0;
}
/**************************************************************************
函数名: mem_read
函数功能: 文件读取函数
函数参数: struct file *filp(open的时候系统产生的结构,根据inode来的)
char __user *buf 存储读取数据的空间
size_t size 读取的大小 size_t应该就是typedef unsigned int的类型
loff_t *ppos 当前文件指针的位置
函数返回值: 返回ret 正常应该是size的值
***************************************************************************/
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;
struct
mem_dev *dev = filp->private_data; //private_data是一个空指针用来接收各种数据的传递
if
(p >= MEMDEV_SIZE) //判断文件指针是否有效
return
0;
if
(count > MEMDEV_SIZE - p)//判断读取的大小是否比剩余的内存大小还大
count = MEMDEV_SIZE - p;
/***********************************************************************************/
/* 判断是否可以读 及阻塞设备 */
/***********************************************************************************/
while
(!canRead) //轮询判断是否 是wakeup真的唤醒了 而不是唤醒别的进程的附近操作 判断阻塞条件
{
if
(filp->f_flags & O_NONBLOCK) //判断文件是否不允许阻塞
{
return
-EAGAIN;
}
wait_event_interruptible(dev->q, canRead); //可以中断的阻塞 第一个参数是阻塞队列 第二个参数是阻塞条件
}
/***********************************************************************************/
/***********************************************************************************/
canRead=0;
if
(copy_to_user(buf, (
void
*)(dev->data + p), count))//把字符设备模块从内核空间拷贝到用户空间
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_EMERG "read %d bytes(s) from %ld\n", count, p);
}
return
ret;
}
/**************************************************************************
函数名: mem_write
函数功能: 文件写函数
函数参数: 参数同read函数
注意:struct file *filp,char __user *buf和size_t size是内核从应用层api-fread中传递下来的
具体是怎么传递的不详 看内核代码 Read_write.c
函数返回值: 返回size为正常执行
***************************************************************************/
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;
struct
mem_dev *dev = filp->private_data;
if
(p >= MEMDEV_SIZE)//MEMDEV_SIZE为内存最大值
return
0;
if
(count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
if
(copy_from_user(dev->data + p, buf, count)) //从应用程序的角度看写是从用户到内核
ret = - EFAULT;
else
{
*ppos += count;// 不知道这里用filp->f_pos+=count行不行 因为我觉得filp->f_pos和ppos是一个地址
ret = count;
printk(KERN_EMERG "written %d bytes(s) from %ld\n", count, p);
}
/***********************************************************************************/
/* 已经写入 可以read了 改变阻塞条件及wakeup */
/***********************************************************************************/
canRead=1;//改变阻塞条件 让READ中的while跳出
wake_up_interruptible(&(dev->q)); //唤醒等待队列
return
ret;
}
/**************************************************************************
函数名: mem_llseek
函数功能: 文件位置指针定位函数
函数参数: struct file *filp
loff_t offset偏移量
int whence 三种可能SEEK_SET SEEK_CUR SEEK_END
函数返回值: 返回0为正常执行
***************************************************************************/
static
loff_t mem_llseek(
struct
file *filp, loff_t offset,
int
whence)
{
loff_t newpos; //定义中间变量
switch
(whence) {
case
0: /* SEEK_SET */
newpos = offset;
break
;
case
1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break
;
case
2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break
;
default
: /* can't happen */
return
-EINVAL;
}
if
((newpos<0) || (newpos>MEMDEV_SIZE)) //判断newpos是否有效
return
-EINVAL;
filp->f_pos = newpos; //赋值 定位
return
newpos;
}
static
const
struct
file_operations mem_fops = //定义此字符设备的file_operations
{ //这里是对结构体整体赋值的方式
.owner = THIS_MODULE,
.llseek = mem_llseek, //函数名都可以自己定义 都是函数指针
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
/**************************************************************************
函数名: memdev_init
函数功能: 内核模块入口函数
函数参数: 无
函数返回值: 返回0为正常执行 返回result为设备号获取不成功
返回- ENOMEM为内存分配不成功
***************************************************************************/
static
int
memdev_init()
{
int
i;
int
result;
dev_t dev_num=MKDEV(mem_major,0);//将主设备号和次设备号转换成32位的设备号给 静态申请设备号用的
if
(mem_major)
result=register_chrdev_region(dev_num,2,"newmemdev");//静态申请设备号为dev_num 2个设备 设备名为“newmemdev”
else
{
result=alloc_chrdev_region(&dev_num,0,2,"newmemdev");//动态分配设备号 设备名可以在/proc/devices文件中找 与/dev路径找文件不同 因为一个设备文件 一个次设备号
mem_major = MAJOR(dev_num);//获得主设备号
}
if
(result < 0)
{
return
result; //判断动态分配是否成功 不成功则退出函数
}
/*设备注册 分配已经在前面完成了 为全局变量*/
cdev_init(&c_dev,&mem_fops);//设备注册初始化 将设备号dev_num和file_operation建立联系
c_dev.owner = THIS_MODULE;//*****************貌似可以屏蔽吧*********************
cdev_add(&c_dev,dev_num,2);//设备注册 添加 设备号为dev_num 设备数为2个
mem_devp = kmalloc(MEMDEV_NR_DEVS *
sizeof
(
struct
mem_dev), GFP_KERNEL);//开辟2个字符设备模块大小的空间
if
(!mem_devp) /*判断是否分配内存成功*/
{
result = - ENOMEM;
goto
fail_malloc;
}
memset(mem_devp, 0, MEMDEV_NR_DEVS *
sizeof
(
struct
mem_dev)); //将初始化2个字符设备模块大小的空间清零
for
(i=0; i < MEMDEV_NR_DEVS; i++) //对字符设备模块结构赋值
{
mem_devp
.size = MEMDEV_SIZE;
mem_devp
.data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//因为data是指针所以继续对其开辟4k空间了
memset(mem_devp
.data, 0, MEMDEV_SIZE);
init_waitqueue_head(&(mem_devp
.q));//初始化等待列队
}
return
0;
fail_malloc: //如果内存分配不成功直接注销设备号
unregister_chrdev_region(dev_num, 1);/*但是不明白的是 1.为什么只注销一个次设备的设备号 2.为什么不先注销cdev_dev*/
return
result;//虽然这里不懂 但不深究了 因为很少出现内存分配不成功的情况(在学习练习的时候)
}
/**************************************************************************
函数名: memdev_exit
函数功能: 内核模块退出函数
函数参数: 无
函数返回值: 无
***************************************************************************/
static
void
memdev_exit(
void
)
{
cdev_del(&c_dev);//设备注销
kfree(mem_devp);//释放开辟的内存空间
/*这里貌似没有释放mem_devp
.data的指针 是不是应该kfree(mem_devp[1].data)*/
/*这里有一个问题就是那两个内存分配 为什么要分配两次 为什么释放一次 我试过貌似第二次不分配也可以*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2);//设备号注销
}
module_init(memdev_init);
module_exit(memdev_exit);
memdev.h
[cpp]
view plain
copy
/**************************************************************************
文件名: memdev.c
日期: 2013/05/12 17:21
头文件: memdev.h
功能: 简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
环境: Redhat企业版5 内核版本2.6.18-53.el5
作者: Hao
流程: 1.分配设备号(a.静态申请 b.动态分配)
2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名” b.自动创建)
3.设备注册(a.设备注册分配 b.设备注册初始化 c.设备注册添加)
4.实现file_operation中的函数
5.设备注销(cdev_del)
6.设备号注销
***************************************************************************/
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*设备数*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096 /*开辟的内存大小*/
#endif
/*mem设备描述结构体*/
struct
mem_dev
{
char
*data;
unsigned
long
size;
wait_queue_head_t q;//定义等待队列 也可以定义成全局变量
};
#endif /* _MEMDEV_H_ */
app_write.c
[cpp]
view plain
copy
#include<stdio.h>
#include<string.h>
int
main()
{
FILE
*fp;
char
buf[4096]="My frist char dev!!!";
fp=fopen("/dev/chardev0","r+");//字符设备文件名为chardev0 所以在/dev 路径中手动创建设备文件时候名字要一致
if
(fp == NULL)
{
printf("Open chardev0 Error!\n");
return
-1;
}
fwrite(buf,
sizeof
(buf),1,fp); //往字符驱动里面写"My frist char dev!!!"
printf("finish write!!\n");
return
0;
}
app_read.c
[cpp]
view plain
copy
#include<stdio.h>
#include<string.h>
int
main()
{
FILE
*fp;
char
buf[4096]="I am is old buf!!!";
fp=fopen("/dev/chardev0","r+");//字符设备文件名为chardev0 所以在/dev 路径中手动创建设备文件时候名字要一致
if
(fp == NULL)
{
printf("Open chardev0 Error!\n");
return
-1;
}
fseek(fp,0,SEEK_SET);//位置清零
printf("finish seek!!\n");
strcpy(buf, "Buf is NULL!");//因为使用同一个buf所以要先清除原值
printf("buf: %s\n",buf);
fread(buf,
sizeof
(buf),1,fp);
printf("finish read!!\n");
printf("new read buf is %s\n",buf);
return
0;
}
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0