简单的字符驱动
设备驱动程序,简称驱动程序,是一个允许高级电脑软件与硬件交互的程序,这种程序创建了一个硬件与硬件,或硬件与软件沟通的界面,经由主板上的总线或其它沟通子系统与硬件形成连接的机制,这样的机制使得硬件设备上的数据交换成为可能。(摘自维基百科–驱动程序)
在Linux中,设备分三种:字符设备、块设备和网络设备。下面主要介绍字符设备。字符设备是能够像字节流(类似文件)一样被访问的设备,有字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read、write系统调用。字符设备可以通过文件系统节点来访问,这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。一个字符设备是一种字节流设备,对设备的存取只能按顺序按字节的存取而不能随机访问,字符设备没有请求缓冲区,所有的访问请求都是按顺序执行的。
这次我们的实验要求是写一个简单的字符设备驱动,动态地装载和卸载该字符设备驱动程序。同时,编写一个测试程序,对该设备驱动程序进行测试,以证明设备驱动程序能正常工作。
下面是驱动程序devDrv.c的代码:
#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 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;
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;
}
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);
下面是makefile文件的主要代码:
# 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.oif
下面是测试驱动程序是否正常的测试代码test.c的核心代码
#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;
}
}
在代码完成之后将这三个文件放在一个文件夹里
进入文件夹所在目录,在终端中打开,以root用户输入make命令开始编译模块
make完之后,利用ls命令查看当前目录下的文件,发现多了一些.ko之类的模块,说明make成功编译了模块
接下来先利用lsmod命令查看系统中已有的模块
接着利用insmod devDrv.ko将编译好的模块装载如系统中,再利用lsmod查看模块,发现了devDrv,说明装载成功。
利用 cat /proc/devices 查看主设备号,发现myDevice的主设备号为250。(什么是主设备号和此设备号呢?简单的来说主设备号对应着一个驱动程序,次设备号对应着该驱动程序下的每个硬件设备。打个比方:主设备号对应着你电脑里的U盘驱动程序,而次设备号就对应着你电脑上插着的各个U盘)
接着是利用mknod /dev/myDevice c 250 0分配从设备号,用ls /dev/查看是否有myDevice
在发现设备中有myDevice之后就可以对字符设备进行测试了。编译首先生成测试程序:gcc test.c –o tl
得到tl可执行程序,再执行:./tl
测试程序首先列出所有的设备名,让我们选中一个,输入myDevice
正确读出之前存放在设备中的字符串!然后让我们输入一个字符串
输入自己想要输入的字符就可以成功将输入的字符读取出来并显示在屏幕上面
从上面的结果可以看出,该字符设备成功的读取了输入的内容,并将其显示出来。所以接下来要做的就是看能不能将该字符设备删除,并将驱动卸载。
首先删除设备,就像删除普通文件一样:rm /dev/myDevice
删除后,看看/dev/目录下是否还有myDevice:ls /dev/
发现dev目录下面没有了myDevice设备了,接着删除模块:rmmod devDrv.
看看模块列表中是否已经没有devDrv模块了:lsmod
下面是博主在学习相关内容时的参考链接