一、实验目的
1)理解设备是文件的概念。
2)掌握Linux模块、驱动的概念和编程流程
3)Windows /Linux下掌握文件读写基本操作
二、实验内容
1)编写一个Linux内核模块,并完成安装/卸载等操作。
2)编写Linux驱动程序并编程应用程序测试。功能:write几个整数进去,read出其和或差或最大值。
3)编写Linux驱动程序并编程应用程序测试。功能:有序读写内核缓冲区,返回实际读写字节数。
提示1:安装时和退出时在内核缓冲区显示不同的字符串。
提示2:相关函数:module_init( )、 module_exit( )
提示3: MODULE_LICENSE( )、 MODULE_AUTHOR ( )等可选
提示4:安装命令:insmod XXXX.ko
提示5:扩展:编写带参数的模块程序
int mytest = 100;
module_param(mytest, int, 0644);
先安装一个必要的软件包(否则会有warning):
sudo apt install libelf-dev
文件code1.c:
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
static char* yourID="shandianchengzi";
module_param(yourID,charp,0644);
static int code1_init(void){
//输出欢迎信息
printk(KERN_ALERT"Hello, dear %s!\n", yourID);
return 0;
}
static void code1_exit(void){
printk(KERN_ALERT"Goodbye, %s!\n", yourID);
}
module_init(code1_init);
module_exit(code1_exit);
Makefile:
obj-m += code1.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
然后运行:
sudo make
1)模块作用:实验文件编译生成的ko模块可接收charp型参数,默认为"shandianchengzi"。当用户加载模块时输出"Hello, dear 参数!\n",退出时输出"GoodBye, 参数!\n"。
2)如何向模块中传入参数:函数原型module_param(name, type, perm)
,参数name,type是自己定义的变量的类型,perm是权限。
其中常用的权限:①0755->用户具有读/写/执行权限,组用户和其它用户具有读/写权限;②0644->用户具有读/写权限,组用户和其它用户具有只读权限;
一般赋予目录0755权限,而文件赋予0644权限。
3)编译:
4)结果:
先用sudo dmesg -C
清空缓冲区,然后使用sudo insmod code1.ko yourID='shandianchengzi'
装入内核并修改参数值,再dmesg
显示当前内容。再使用sudo rmmod code1.ko
卸载内核,再dmesg
显示当前内容。与预期相符。
参考:
主要:Linux中添加一个带参数的模块
传入字符串的设计:第四章Linux内核模块之五(模块参数)
提示1:参考任务1
提示2:至少实现xx_open,xx_write,xx_read等函数
提示3:功能:
xx_write( )写进去2个整数
xx_read( )读回结果(和或差或最大值)
提示4: [可选的设备注册方式,其余方式参考baidu]
struct miscdevice mydemodrv_misc_device ;
ret = misc_register( &mydemodrv_misc_device )
code2.c:
#include
#include
#include
#include
#include
#define NAME "code2"
MODULE_LICENSE("GPL");
static struct device *mydemodrv_device;
static int a=0,b=0;
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static int demodrv_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t lbuf, loff_t *ppos)
{
printk("%s: a+b=%d\n", __func__,a+b);
return 0;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
if(count>64)
{
count = 64;
}
char temp[64];
if(copy_from_user(temp,buf,count))
{
return -EFAULT;
}
sscanf(temp, "%d%d",&a,&b);
printk("%s: a=%d,b=%d\n", __func__,a,b);
return count;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.release = demodrv_release,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = NAME,
.fops = &demodrv_fops,
};
static int __init code2_init(void)
{
int ret;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register code2 misc device\n");
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", NAME);
return 0;
}
static void __exit code2_exit(void)
{
printk("removing device: %s\n", NAME);
misc_deregister(&mydemodrv_misc_device);
}
module_init(code2_init);
module_exit(code2_exit);
测试程序:
test.c:
#include
#include
#include
#define DEV_NAME "/dev/code2"
int main()
{
char buffer[64];
int fd,a,b;
fd = open(DEV_NAME, O_RDWR | O_CREAT);
if (fd < 0) {
printf("open device %s failded\n", DEV_NAME);
return -1;
}
printf("请输入两个整数:");
scanf("%d%d",&a,&b);
sprintf(buffer,"%d %d",a,b);
write(fd,buffer,64);
read(fd, buffer, 64);
close(fd);
return 0;
}
1)编译:
2)结果:完成的是求和功能。
3)遇到的问题:最开始,打开文件指针的时候用了O_RDONLY,只读。后来写write的时候死活读不进去。。。
怀疑是中间文件没删、模块代码返回值不能size_t强转ssize_t等问题,结果原来是因为这个。。。。。。。
改成O_RDWR | O_CREAT。
参考:《内核kernel:misc机制字符设备驱动模块编写》
O_RDWR | O_CREAT参考:3.8 write函数-文件数据写
4)用的方法是先读入缓冲区,再用sscanf读。我觉得传整型指针有点麻烦,要写两个write。
5)设备读不了写不了装不了用不了没输出:
甚至把模块卸载了,它还在!
这是因为在装入模块之前,不慎调用test程序,已经提前申请打开了设备,以致于装载的时候未装载成功。
如果出现这种情况,运行:
sudo rm /dev/code2
提示1:参考任务1
提示2:至少实现xx_open,xx_write,xx_read等函数
提示3:功能:
内核分配一定长度的缓冲区,比如64字节。
xx_write()写进去若干字符,注意维护写入位置。下次继续写的话,接着该位置往后写,直到缓冲区末尾。要返回实际写入字数。
xx_read()读出若干字符串,注意维护读出位置。下次继续读的话,接着该位置往后读,直到缓冲区末尾。要返回实际读回字数。
扩展:
▲缓冲区设置为循环缓冲区?
▲如何避免写覆盖,避免读重复?
code3.c
#include
#include
#include
#include
#include
#include /*kmalloc*/
#define NAME "code3"
MODULE_LICENSE("GPL");
static struct device *mydemodrv_device;
static char *device_buffer;
static size_t pos;
#define MAX_DEVICE_BUFFER_SIZE 64
static int demodrv_open(struct inode *inode, struct file *file)
{
device_buffer = kmalloc(MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
pos=0;
return 0;
}
static ssize_t
demodrv_read(struct file *file, char __user *buf, size_t lbuf, loff_t *ppos)
{
if(pos < lbuf)
{
lbuf = pos;
}
if(copy_to_user(buf,device_buffer+pos-lbuf,lbuf))
{
return -EFAULT;
}
pos=pos-lbuf;
printk("%s: 读出%ld字节,读出后指针位置为%ld\n", __func__, lbuf, pos);
return lbuf;
}
static ssize_t
demodrv_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
if(pos + count > 64)
{
count = 64-pos;
if(count < 0)
return count;
}
if(copy_from_user(device_buffer+pos,buf,count))
{
return -EFAULT;
}
pos=pos+count;
printk("%s: 写入%ld字节,写完后缓冲区为%s\n", __func__, count, device_buffer);
return count;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = NAME,
.fops = &demodrv_fops,
};
static int __init code3_init(void)
{
int ret;
ret = misc_register(&mydemodrv_misc_device);
if (ret) {
printk("failed register code2 misc device\n");
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("succeeded register char device: %s\n", NAME);
return 0;
}
static void __exit code3_exit(void)
{
kfree(device_buffer);
printk("removing device: %s\n", NAME);
misc_deregister(&mydemodrv_misc_device);
}
module_init(code3_init);
module_exit(code3_exit);
测试程序:
test.c:
#include
#include
#include
#include
#define DEV_NAME "/dev/code3"
int main()
{
char buffer[64];
int fd,a,b;
fd = open(DEV_NAME, O_RDWR | O_CREAT);
if (fd < 0) {
printf("open device %s failded\n", DEV_NAME);
return -1;
}
printf("请输入向缓冲区写的内容:");
scanf("%s",buffer);
write(fd,buffer,strlen(buffer));
read(fd, buffer, 64);
printf("从缓冲区读:%s\n", buffer);
close(fd);
return 0;
}
1)kmalloc头文件是#include
2)编译:
3)运行:
4)遇到的问题2:copy_to_user和copy_from_user的to和from参数是反过来的,一开始没注意。
5)遇到的问题3:
下面这个不行:
if(pos - lbuf < 0)
{
lbuf = pos;
}
改成这个才行:
if(pos < lbuf)
{
lbuf = pos;
}
因为size_t
是unsigned类型的,所以不能直接跟0比较。