编写
mkdir driver && cd driver
1. 简单驱动程序
说明:
- 只有加载和卸载功能
- 简单驱动程序
hello_driver.c
vim hello_driver.c
#include<linux/module.h>
//驱动程序初始化函数
static int __init hello_init(void)
{
printk(KERN_INFO"welcome to Hello character driver!\n");
return 0;
}
//驱动程序退出函数
void __exit hello_exit(void)
{
printk("Goodbye!Character driver is so easy!\n");
}
//向系统登记函数
module_init(hello_init);
module_exit(hello_exit);
//遵守GPL开源协议
MODULE_LICENSE("Dual BSD/GPL");
- 驱动程序
Makefile
vim Makefile
obj-m:=hello_driver.o
KDIR:=/lib/modules/$(shell uname -r)/build
SRCPWD:=$(shell pwd)
all:
make -C $(KDIR) M=$(SRCPWD) modules
clean:
rm -f *.o *.mod.o *.mod.c *.symvers Mo* mo*
echo
ls -lh
cleanall:
make clean
rm -f *.ko
echo
ls -lh
insmod:
insmod hello_driver.ko
lsmod | more
rmmod:
rmmod hello_driver
提示:
- 注意Makefile格式中的Tab位置
- 示例文件的云盘链接
mwcj
2. 驱动程序进阶1
说明:
- 增加修改设备属性功能
- 源代码
hello_driver.c
#include<linux/module.h>
#include<linux/fs.h>
#define DEVICE_NAME "hello"
static int demoMajor=0;
static long hello_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case 0:
printk("command 0 is run!\n");
break;
case 1:
printk("command 1 is run!\n");
break;
default:
printk("not known command!\n");
break;
}
return 0;
}
//向系统登记hello_ioctl函数
static struct file_operations hello_fops=
{
owner:THIS_MODULE,
unlocked_ioctl:hello_ioctl,
};
//驱动程序初始化函数
static int __init hello_init(void)
{
demoMajor=register_chrdev(0,DEVICE_NAME,&hello_fops);
if(demoMajor<0)
{
printk(KERN_NOTICE DEVICE_NAME"register failure\n");
return demoMajor;
}
return 0;
}
//驱动程序退出函数
void __exit hello_exit(void)
{
if(demoMajor>0)
unregister_chrdev(demoMajor,DEVICE_NAME);
}
//向系统登记函数
module_init(hello_init);
module_exit(hello_exit);
//遵守GPL开源协议
MODULE_LICENSE("Dual BSD/GPL");
-
Makefile
不变 - 编译
make
- 加载驱动
make insmod
- 查看注册设备
more /proc/devices
Enter
或d
翻行
可以看到有hello,成功!!!
使用设备
由于本驱动程序没有使用次设备号,故建立设备文件时,次设备号的值并不重要,可以取有效范围的任意值,本次就取0
cd /dev
mknod hello c 242 0
测试程序
testHelloDevice.c
vim testHelloDevice.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#define DEVICE_FILE "/dev/hello"
int main()
{
int fd,cmd;
//打开设备文件
fd=open("/dev/hello",O_RDWR);
if(fd<=0)
{
perror(DEVICE_FILE);
exit(1);
}
//使用ioctl修改硬件属性
for(cmd=0;cmd<=4;cmd++)
{
sleep(1);
ioctl(fd,cmd,NULL);
}
//关闭设备文件
close(fd);
return 0;
}
gcc -o test testHelloDevice.c
./test
dmesg | tail
提示:
- mknod命令格式:
mkmod <设备文件名> <设备类型> <主设备号> <次设备号>
- 为了方便管理,设备文件名与设备名相同
- 设备类型为
c
(字符设备)或b
(块设备)
参阅:
3. 驱动程序进阶2
说明:
- 增加读写接口
- 源代码
hello_driver.c
vim hello_driver.c
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/poll.h>
#include<linux/string.h>
#define DEVICE_NAME "hello"
//用于保存虚拟硬件返回给应用程序的字符串
static char drv_buff[6]="abcde";
//用于接收应用程序发送给虚拟硬件的字符串数据
static char data_from_user[1024];
//虚拟硬件返回给应用程序的字符串的格式标志
static unsigned char data_format=0;
static int demoMajor=0;
//硬件设备属性修改
static long hello_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case 0:
if(data_format!=0)
{
data_format=0;
strcpy(drv_buff,"abcde");
}
break;
case 1:
if(data_format!=1)
{
data_format=1;
strcpy(drv_buff,"ABCDE");
}
break;
default:
break;
}
return 0;
}
//读接口函数
static ssize_t hello_read(struct file *filp,char *buffer,size_t count,loff_t *ppos)
{
int real_count,i;
real_count=sizeof(drv_buff);
i=copy_to_user(buffer,drv_buff,real_count);
if(i<0)
printk("copy_to_user() failure!\n");
return(ssize_t)real_count;
}
//写接口函数
static ssize_t hello_write(struct file *filp,const char *buffer,size_t count,loff_t *ppos)
{
int i;
memset(data_from_user,'\0',sizeof(data_from_user));
i=copy_from_user(data_from_user,buffer,count);
if(i<0)
printk("copy_from_user()failure!\n");
printk(data_from_user);
return(ssize_t)count;
}
//向系统注册ioctl、read、write函数
static struct file_operations hello_fops=
{
owner:THIS_MODULE,
unlocked_ioctl:hello_ioctl,
read:hello_read,
write:hello_write,
};
static int __init hello_init(void)
{
demoMajor=register_chrdev(0,DEVICE_NAME,&hello_fops);
if(demoMajor<0)
{
printk(KERN_NOTICE DEVICE_NAME"register failure\n");
return demoMajor;
}
return 0;
}
void __exit hello_exit(void)
{
if(demoMajor>0)
unregister_chrdev(demoMajor,DEVICE_NAME);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
- 重新编译
make
- 卸载旧版本
make rmmod
- 加载驱动
make insmod
提醒:
- 加载新驱动程序前记得卸载旧版本
- 在系统硬件没有太大的改变的情况下,每次加载驱动程序动态获得的主设备号一般不会改变,即对应的设备文件仍可用,若主设备号改变,则需删除旧版本的设备文件,以新主设备号重建设备文件.
- 测试程序
testHelloDevice.c
vim testHelloDevice.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#define DEVICE_FILE "/dev/hello"
#define BUFFER_SIZE 1024
int main()
{
int fd;
char buff[BUFFER_SIZE];
//打开设备文件
fd=open("/dev/hello",O_RDWR);
if(fd<=0)
{
perror(DEVICE_FILE);
exit(1);
}
//读硬件设备默认状态下的数据
sleep(1);
memset(buff,'\0',BUFFER_SIZE);
read(fd,buff,BUFFER_SIZE);
printf("read a default string:%s\n",buff);
//发送修改硬件属性的命令1
sleep(1);
ioctl(fd,1,NULL);
//读命令1后硬件设备的数据
memset(buff,'\0',BUFFER_SIZE);
read(fd,buff,BUFFER_SIZE);
printf("read a string after cmd 1:%s\n",buff);
//发送修改硬件属性的命令0
sleep(1);
ioctl(fd,0,NULL);
//读命令0后硬件设备的数据
memset(buff,'\0',BUFFER_SIZE);
read(fd,buff,BUFFER_SIZE);
printf("read a string after cmd 0:%s\n",buff);
//往硬件设备写入字符串
memset(buff,'\0',BUFFER_SIZE);
strcpy(buff,"I love Linux!");
printf("write string \"%s\" to the device\n",buff);
write(fd,buff,strlen(buff));
//关闭设备文件
close(fd);
return 0;
}
- 编译运行
gcc -o test testHelloDevice.c
./test
- 查看日志
dmesg | tail
调试
- 使用dmesg命令查看系统消息缓冲区中prink()的输出。当缓冲区已满的时候才真正写入日志文件,因此通过日志文件有时候会看到消息,有时候看不到消息.
dmesg | tail
或
tail -f /var/log/messages
Q&A
1. *** /lib/modules/3.10.0-957.21.3.el7.x86_64/build: 没有那个文件或目录。
- 调试-查看内核版本信息
uname -r
- 查看系统内核源码
cd /lib/modules/ && ls
- 进入当前使用的内核目录
rm build
ln -s /usr/src/kernels/3.10.0-957.21.3.el7.x86_64 /lib/modules/3.10.0-957.21.3.el7.x86_64/build
- 编译
make
成功!!!
参阅:
2. disagrees about version of symbol modulelay
- 加载驱动程序出错
insmod hello_driver.ko
-
调试-查看出错的日志信息
cat /var/log/messages | tail
调试-查看/usr/src/kernels下内核源码
发现不存在内核开发包
yum install kernel-devel -y
再次查看结果
- 重新生成软链接编译后,开始加载
insmod hello_driver.ko
lsmod | more
没有报错,且系统内核已加载的模块中有hello_driver,说明成功!!!
参阅:
3. 错误:初始值设定项里有未知的字段‘ioctl’
- 查看系统内核
uname -r
- 查看
file_operations
vim /usr/src/kernels/$(uname -r)/include/linux/fs.h
/_ioctl
可以看出在当前系统内核中fs.h中不存在
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
- 对应自己系统中的
fs.h
文件修改驱动程序中ioctl
部分
提示:
- ioctl消失的版本是v2.6.35到v2.6.36-rc1间
参阅:- linux驱动错误: 初始值设定项里有未知的字段‘ioctl’
更新中......