您的位置:首页 > 运维架构 > Linux

linux简单的字符设备驱动程序

2012-02-24 15:43 357 查看
要求:掌握添加设备驱动程序的方法

内容:

采用模块方法,添加一个新的设备驱动程序。

要求添加字符设备的驱动。

编写一个应用程序,测试添加的驱动程序。

linxu系统中,在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:

对设备初始化和释放.

把数据从内核传送到硬件和从硬件读取数据.

读取应用程序传送给设备文件的数据和回送应用程序请求的数据.

检测和处理设备出现的错误.

设备分为两种:字符设备和块设备,这里就以简单的字符设备为例。

在设备驱动程序中有一个非常重要的结构file_operations,该结构的每个域都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。

struct file_operations {
int(*seek) (struct inode * ,structfile *, off_t ,int);

int (*read) (struct inode * ,structfile *, char ,int);

int (*write) (struct inode * ,structfile *, off_t ,int);
……

}

编写设备驱动程序的主要工作是编写子函数,并填充file_operations的各个域
例如:
Structfile_operations my_fops={
.read=my_read,
.write=my_write,
.open=my_open,
.release=my_release
}

然后再定义函数my_read,my_write,my_open,my_release相应的函数体。
例如:
staticssize_t my_open(struct inode *inode,struct file *file){
staticint counter=0;
if(Device_Open)
return-EBUSY;
Device_Open++;
/*写入设备的信息*/
sprintf(msg,"the device hasbeen called %d times\n",counter++);
msg_ptr=msg;
return0;
}

同时对于可卸载的内核模块(LKM),至少还有两个基本的模块:

my_init用于注册设备,获得设备的主设备号
调用register_chrdev(0,“sky_driver(设备名)”,&my_fops);
my_exit用于注销设备
调用unregister_chrdev(my_major,“sky_driver(设备名)”);
然后在程序尾再调用这两个函数
Module_init(my_init);
Module_exit(my_exit)
MODULE_LICENSE(“GPL”);
不过如果使用特殊函数名
init_module 和cleanup_module,
就不用在程序最后加上上面那三行代码了。

废话少说,以下是实验过程:

首先编写驱动程序。

将后面附上的3个源程序放在一个文件夹中后(也可直接打包下载http://download.csdn.net/detail/creazyapple/4088141)

,开始进行实验:

1.编译驱动程序。

在控制台下进入文件所在目录,输入命令make。如图:



看到了吗?提示错误。原来需要用超级用户权限。



没有错误提示,说明make成功,进入文件夹下可以看到多了一些.k,.ko之类的文件。这就是编译好的模块。




2. 装载模块。

同样,必须要是root下才能进行。装载前,我们可以先看看系统中的模块:lsmod

装载,输入命令: insmod devDrv.ko

完成后,再看看系统中的模块,发现多了一个模块“devDrv”,表示我们成功加载了!



可以用命令查看系统日志信息:dmesg

看到如下信息:



这正是在我们的驱动程序中注册函数输出的内容哦!

3.分配次设备号。

每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序.。

分配前,我们必须要知道主设备号(其实在上一步中我们从dmesg中已经看到主设备好为250): cat /proc/devices

可见到这一行:250 myDevice,是了,主设备号为250.

现在我们分配从设备号:mknod /dev/myDevice c 250 0

实际上就是在虚拟文件夹/dev/中加一个设备(在系统看来是文件)myDevice ,注意,这个是设备名,在测试文件中真是要利用这个设备名打开设备。最后的0表示从设备号。可以随便分配(只要不冲突)。

此后便可以在/dev/目录下看到新建的设备myDevice了:ls /dev/



一切成功的话,就可以测试我们的驱动程序啦!

4.测试驱动程序。

首先要编译测试程序:gcc drvTest.c -o sb

得到sb可执行程序,再执行 :./sb

测试程序首先列出所有的设备名,让我们选中一个,当然输入myDevice啦:



正确读出之前存放在设备中的字符串!然后提示让我们输入一个字符串,随便啦:



最后是不是正确保存、读出了呢?of course !而且还告诉我们调用了几次这个设备呢!



5.删除设备、模块。

首先删除设备。就像删除普通文件一样:rm /dev/myDevice

删除后,看看/dev/目录下是佛已经木有myDevice 啦?:ls /dev/



接着删除模块:rmmod devDrv.

看看模块列表中是否已经没有devDrv模块了?:lsmod



在本次实验中,还特别添加了一些代码来给设备上锁,实现互斥,以免同时有多个进程读取设备造成混乱。(一般来说,字符设备如打印机是不能共享滴。。)观察驱动程序devDrv.c中两个函数

static int my_open(struct inode *inode, struct file *file)

{

if(mutex)

return -EBUSY;

mutex = 1;//上锁

printk("<1>main device : %d\n", MAJOR(inode->i_rdev));

printk("<1>slave device : %d\n", MINOR(inode->i_rdev));

printk("<1>%d times to call the device\n", ++counter);

try_module_get(THIS_MODULE);

return 0;

}

/* 每次使用完后会release */

static int my_release(struct inode *inode, struct file *file)

{

printk("Device released!\n");

module_put(THIS_MODULE);

mutex = 0;//开锁

return 0;

}
int 型mutex就是用来上锁的,它被初始化为0,一旦使用该设备,就首先要调用my_open函数,将mutex设置为1,如果此后还想使用该设备,必然因为mutex为1而返回-EBUSY,而终止。使用完该设备后,调用my_release释放设备,将mutex重新设回0.

特别注意,还有一个counter变量,使用来计量使用次数的。

makefile的格式相当严格,比如不能用8个空格来代替一个table键等。

makefile最后有一句:



devDrv一定要对应我们要编译的驱动程序devDrv.c的文件名。

源代码附上。共三个文件,将其放在一个文件夹中

1.驱动程序devDrv.c

/*
*  devDrv.c
*  threeProcess
*  Created on: 2012-2-24
*      Author: zhushengben
*/

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/init.h"
#include "linux/types.h"
#include "linux/errno.h"
#include "linux/uaccess.h"
#include "linux/kdev_t.h"
#define MAX_SIZE 1024

static int my_open(struct inode *inode, struct file *file);
static int my_release(struct inode *inode, struct file *file);
static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);
static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);

static char message[MAX_SIZE] = "-------congratulations--------!";
static int device_num = 0;//设备号
static int counter = 0;//计数用
static int mutex = 0;//互斥用
static char* devName = "myDevice";//设备名

struct file_operations pStruct =
{ open:my_open, release:my_release, read:my_read, write:my_write, };

/* 注册模块 */
int init_module()
{
int ret;
/* 函数中第一个参数是告诉系统,新注册的设备的主设备号由系统分配,
* 第二个参数是新设备注册时的设备名字,
* 第三个参数是指向file_operations的指针,
* 当用设备号为0创建时,系统一个可以用的设备号创建模块 */
ret = register_chrdev(0, devName, &pStruct);
if (ret < 0)
{
printk("regist failure!\n");
return -1;
}
else
{
printk("the device has been registered!\n");
device_num = ret;
printk("<1>the virtual device's major number %d.\n", device_num);
printk("<1>Or you can see it by using\n");
printk("<1>------more /proc/devices-------\n");
printk("<1>To talk to the driver,create a dev file with\n");
printk("<1>------'mknod /dev/myDevice c %d 0'-------\n", device_num);
printk("<1>Use \"rmmode\" to remove the module\n");

return 0;
}
}
/* 注销模块,函数名很特殊 */
void cleanup_module()
{
unregister_chrdev(device_num, devName);
printk("unregister it success!\n");
}

static int my_open(struct inode *inode, struct file *file)
{
if (mutex)
return -EBUSY;
mutex = 1;//上锁
printk("<1>main  device : %d\n", MAJOR(inode->i_rdev));
printk("<1>slave device : %d\n", MINOR(inode->i_rdev));
printk("<1>%d times to call the device\n", ++counter);
try_module_get(THIS_MODULE);
return 0;
}
/* 每次使用完后会release */
static int my_release(struct inode *inode, struct file *file)
{
printk("Device released!\n");
module_put(THIS_MODULE);
mutex = 0;//开锁
return 0;
}

static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f)
{
if(copy_to_user(user,message,sizeof(message)))
{
return -EFAULT;
}
return sizeof(message);
}

static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f)
{
if(copy_from_user(message,user,sizeof(message)))
{
return -EFAULT;
}
return sizeof(message);
}

2.makefile文件。注意,文件名就叫makefile或者Makefile,大小写不可随意更改,关于makefile的编写,参考

http://wiki.ubuntu.org.cn/index.php?title=%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:MakeFile%E4%BB%8B%E7%BB%8D&variant=zh-cn

# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
# called from kernel build system: just declare what our modules are
obj-m := devDrv.o
endif


3.测试程序test.c

/*
*  drvTest.c
*  threeProcess
*  Created on: 2012-2-24
*      Author: zhushengben
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define MAX_SIZE 1024

int main(void)
{
int fd;
char buf[MAX_SIZE];
char get[MAX_SIZE];
char devName[20], dir[50] = "/dev/";
system("ls /dev/");
printf("Please input the device's name you wanna to use :");
gets(devName);
strcat(dir, devName);
fd = open(dir, O_RDWR | O_NONBLOCK);
if (fd != -1)
{
read(fd, buf, sizeof(buf));
printf("The device was inited with a string : %s\n", buf);
/* 测试写 */
printf("Please input a string  :\n");
gets(get);
write(fd, get, sizeof(get));
/* 测试读 */
read(fd, buf, sizeof(buf));
system("dmesg");
printf("\nThe string in the device now is : %s\n", buf);
close(fd);
return 0;
}
else
{
printf("Device open failed\n");
return -1;
}
}


参考资料:

linux设备驱动程序(hello world ):/article/7347351.html

linux设备驱动程序:http://www.knowsky.com/340884.html

file_operation 结构详解:http://www.chineselinuxuniversity.net/articles/45406.shtml

linux字符设备驱动程序:/article/9738433.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: