1.字符设备与块设备
Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得设备操作犹如文件一般。在应用程序看来,硬件设备就是一个普通的文件,应用程序可以像操作普通文件一样操作设备文件。
设备可分为字符设备与块设备,字符设备是按照字符流顺序访问,如键盘。而块设备是能够随机访问数据片。字符设备驱动程序的访问方式最接近普通文件,而块设备驱动程序是支持文件系统的结构,以文件系统实现与应用程序的连接。应用程序不能直接使用块设备驱动程序。
2. 字符设备的接口函数
注册字符设备:
int register_chrdev(unsigned int major,const char* name,struct file_operations *fops)
解释:
major是主设备号,当该值为0时,自动运行分配。
name是驱动的名字,出现/proc/devices
fops是file_operations结构
返回值:
如果major为0,正常注册后,返回分配的主设备号。如果分配失败,即有注册的设备,返回-EBUSY. 如果major值大于255,返回-EINVAL. 注册成功后返回0.
注销设备
int unregister_chrdev(unsigned int major,const char* name)
解释:
major是主设备号
name是驱动的名字
返回值:若major大于255,返回-EINVAL,正常注销后返回0。
file_operations结构体中的一些函数,主要有open(),release,read(),write().ioctl(),llseek,poll().
int (*open)(struct inode*,struct file*filp):
解释:
inode为索引节点结构的指针,参数file是指向这一设备文件结构的指针。
返回值:正常返回值为0,否则返回小于0的值。
open函数通常的功能是:
检查设备特定的错误(如设备没准备好,或者类似的硬件错误)
如果它第一次打开,初始化设备
如果需要,更新f_op指针
分配并填充要放进filp->private_data的任何数据结构
int(*release)(struct inode*inode,struct file*filp);
解释:
应用程序关闭设备文件时,终止使用设备驱动程序
inode为索引节点指针,参数file是指向这一设备文件结构的指针。
返回值:正常返回0,与应用程序调用close()函数是相对应的。
release函数的功能是:
释放open分配在filp->private_data中的任何东西
在最后的close时关闭设备
ssize_t read(struct file*filp, char* buf,size_t count,loff_t *f_pos);
ssize_t write(struct file* filp,const char* buf,size_t count,loff_t * f_pos);
read函数是应用程序读取设备文件时调用的函数,而write函数是应用程序向设备文件写入数据时调用的函数
解释:
filp是文件指针,count是请求数据的大小,buf是用户空间的缓存指针,f_pos是文件位置变量。
返回值:函数正常运行时,返回已经处理的数据量,否则返回小于0的值。
unsigned long copy_to_user(void__user* to,const void* from,unsigned long count);
unsigned long copy_from_user(void* to,const void__user* from,unsigned long count);
copy_to_user是把内核空间的数据写入到用户空间。
copy_from_user是从用户空间把数据写入到内核空间。
copy_to_user参数:
to 是用户内存空间的地址。
from是内核内存空间的地址。
count是写入用户空间数据的数量
copy_from_user参数:
to是内核空间的地址
from是用户空间的地址
count是写入到内核空间的数据数量
返回值: 如果复制成功,则返回0,否则返回没有复制成功的字节数。
宏get_user(x,ptr)与put_user(x,ptr).
get_user是从ptr指向的用户空间读取x变量大小的数据。
put_user(x,ptr)是将x变量大小的数据写入到ptr所指向的用户空间中去。
3.并发控制的例子
字符设备驱动文件:
globalsem.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");
#define MAJOR_NUM 1000 //定义主设备号
static int globalvar_open(struct inode*inode,struct file*filp);
static int globalvar_release(struct inode*inode,struct file*filp);
static ssize_t globalvar_read(struct file*,char*,size_t,loff_t *off);
static ssize_t globalvar_write(struct file*,const char*,size_t,loff_t*);
struct file_operations globalvar_fops={
.read=globalvar_read,
.write=globalvar_write,
.open=globalvar_open,
.release=globalvar_release,
};
static int global_var=0;
static int globalvar_count=0;
static struct semaphore sem; //定义信号量
spinlock_t spin=SPIN_LOCK_UNLOCKED; //定义自旋锁,并且初始化自旋锁,也可以用spin_lock_init(&lock)动态初始化自旋锁
static int __init globalvar_init(void){
int ret;
ret=register_chrdev(MAJOR_NUM,"global_var",&globalvar_fops); //如果注册成功则返回0
if(ret){
printk("globalvar register failure");
}
else {
printk("globalvar register success");
sema_init(&sem,1); //初始化信号量
//spin_lock_init(&spin);
//init_MUTEX(&sem);
}
return 0;
}
static void __exit globalvar_exit(void){
int ret;
ret=unregister_chrdev(MAJOR_NUM,"globalvar"); //注销设备
if(ret){ //如果注销成功则返回0
printk("globalvar unregister failure");
}
else{
printk("globalvar unregister success");
}
}
//从设备中读取数据,filp是文件指针,buf是用户空间缓冲区,len是要读取数据的长度,off是文件位置偏移量
static ssize_t globalvar_read(struct file *filp,char*buf,size_t len,loff_t *off){
if(down_interruptible(&sem)){ //申请信号量,以任务可被信号中断的方式申请信号量,成功时返回0
return -ERESTARTSYS;
}
if(copy_to_user(buf,&global_var,sizeof(int))){ //将数据复制到用户空间
up(&sem);
return -EFAULT;
}
up(&sem);
return sizeof(int);
}
static ssize_t globalvar_write(struct file* filp,char*buf,size_t len,loff_t *off){
if(down_interruptible(&sem)){
return -ERESTARTSYS;
}
if(copy_from_user(&global_var,buf,sizeof(int))){
up(&sem);
return -EFAULT;
}
up(&sem);
return sizeof(int);
}
//应用程序中调用open函数就会调用驱动程序中的open函数
static int globalvar_open(struct inode*inode,struct file*filp){
spin_lock(&spin);//获得自旋锁
if(globalvar_count){//通过globalvar_count对打开的设备进行控制,某一时刻,只能有一个进程打开设备
spin_unlock(&spin);//释放自旋锁
return -EBUSY;
}
globalvar_count++;
spin_unlock(&spin);
return 0;
}
//应用程序中调用close函数就会调用此release函数
static int globalvar_release(struct inode*inode,struct file*filp){
globalvar_count--;
return 0;
}
测试文件:
globalvartest.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd,num;
fd=open("/dev/globalvar",O_RDWR,S_IRUSR|S_IWUSR);
if(fd!=-1){
read(fd,&num,sizeof(int));
printf("the globalvar is%d/n",num);
printf("please input the num written to globalvar/n");
scanf("%d",&num);
write(fd,&num,sizeof(int));
read(fd,&num,sizeof(int));
printf("the globalvar is %d/n",num);
close(fd);
}
else{
printf("device open failure/n");
}
}
测试方法:
(1)将globalsem.c与globalvaltest.c文件放到/usr/src/kernels/linux-2.6.20/drivers/char目录下。
并在Makefile文件中添加obj-m +=globalsem.o
(2)返回到/usr/src/kernels/linux-2.6.20下make.
(3)插入模块insmod globalsem.ko,然后 mknod /dev/globalvar c 200 0
200是major值。
(4)编译globalvartest, gcc -o globalvartest globalvartest.c
(5)运行两个globalvartest.c 进行测试,发现会出现device open failure错误。