- 实现一组对外的访问接口, open、release、read、write将这些函数的实现赋值给file_operations这个结构体中。
- 实现对字符设备驱动程序的创建和初始化。
- 按照内核模块的编写方法去制定对应的入口函数和出口函数。入口函数实现对驱动程序的注册功能,需要调用驱动程序的初始化函数进行初始化操作。出口函数就是完成对程序的卸载;
- 编译对应的驱动程序源码,从而得到.ko文件,查看/proc/devices文件,从而判断对应的驱动程序是否注册成功,若成功,在/dev目录下将会看到创建对应的设备文件以及设备号,通过mknod命令实现
art.c
//art.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#define TEST_DEVICE_FILENAME "/dev/art_dev" //设备文件名
#define BUFF_SIZE 1024
int main()
{
int fd,nwrite,nread;
char buff[BUFF_SIZE]; //缓冲区
/* 打开文件 */
fd=open("/dev/art_dev",O_RDWR);
if(fd<0){
perror("open");
exit(1);
}
do{
printf("向内核输入字符内容为('q'退出):");
memset(buff,0,BUFF_SIZE);
if(fgets(buff,BUFF_SIZE,stdin)==NULL){
perror("error fgets");
break;
}
buff[strlen(buff)-1]='\0';
if(write(fd,buff,strlen(buff))<0) //向内核设备写入数据
{
perror("error write");
break;
}
if(read(fd,buff,BUFF_SIZE)<0) //从内核设备读取数据
{
perror("error write");
break;
}
else{
printf("从内核中读出字符内容为:%s\n\n",buff);
}
}while(strncmp(buff,"q",1));
close(fd);
exit(0);
}
art_dev.c
//art_dev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define TEST_DEVICE_NAME "art_dev"
#define BUFF_SIZE 1024
// 全局变量
static struct cdev art_dev;
unsigned int major = 0;//主设备号
static char *data = NULL;//内核空间大小
dev_t dev;//起始的设备编号
/* 写函数 */
static ssize_t art_write(struct file *file, const char *buffer, size_t count, loff_t *f_pos)
{
if(count<0)
{
return -EINVAL;
}
memset(data,0,BUFF_SIZE);
count=(BUFF_SIZE>count) ?count:BUFF_SIZE;
if(copy_from_user(data,buffer,count)) //将用户缓冲的数据复制到内核空间
{
return -EFAULT;
}
return count;
}
/* 读函数 */
static ssize_t art_read( struct file *file, char *buf, size_t count, loff_t *f_pos)
{
int len;
if(count<0)
{
return -EINVAL;
}
len=strlen(data);
count=(len>count)?count:len;
if(copy_to_user(buf,data,count)) //将内核缓冲的数据复制到用户空间
{
return -EFAULT;
}
return count;
}
/* 打开函数 */
static int art_open(struct inode *inode,struct file *file)
{
printk("This is open operation.\n");
/* 分配并初始化缓冲区 */
data=(char*)kmalloc(sizeof(char)*BUFF_SIZE,GFP_KERNEL);
if(!data)
{
printk("malloc error!");
return -ENOMEM;
}
memset(data,0,BUFF_SIZE);
return 0;
}
/* 关闭函数 */
static int art_release(struct inode *inode,struct file *file)
{
printk("This is release operation.\n");
if(data)
{
kfree(data); //释放缓冲区
data=NULL; //防止出现野指针
}
return 0;
}
/* 虚拟设备的file——operation结构 */
static struct file_operations art_fops=
{
.owner=THIS_MODULE,
.read=art_read,
.write=art_write,
.open=art_open,
.release=art_release,
};
/* 创建、初始化字符设备,并且注册到系统 */
static void art_setup_cdev( struct cdev *cdev, int minor, struct file_operations *fops)
{
int error;
cdev_init(cdev,fops);
cdev->owner=THIS_MODULE;
cdev->ops=fops;
error=cdev_add(cdev,dev,1);
if(error)
{
printk(KERN_NOTICE"Error %d adding test %d",error,minor);
}
}
//模块注册入口
static int __init _init_artmodule(void)
{
int result;
dev=MKDEV(major,0);
if(major)
{
//静态注册一个设备,设备号事先指定好,用cat/proc/devices来查看
result=register_chrdev_region(dev,1,TEST_DEVICE_NAME);
}
else
{ //动态分配一个设备号
result=alloc_chrdev_region(&dev,0,1,TEST_DEVICE_NAME);
}
if(result<0)
{
printk(KERN_WARNING"Test device:unable to get major %d\n",major);
return result;
}
art_setup_cdev(&art_dev,0,&art_fops);
printk("The major of the test device is %d\n",dev);
return 0;
}
/* 卸载模块 */
static void __exit _cleanup_artmodule(void)
{
cdev_del(&art_dev);
unregister_chrdev_region(MKDEV(major,0),1);
printk("Test device uninstalled.\n");
}
module_init(_init_artmodule);
module_exit(_cleanup_artmodule);
MaKefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
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
.PHONE:
modules modules_install clean
else
obj-m := art_dev.o
endif
字符设备调试步骤
360云盘下载,点击前复制密码 访问密码 4e86