在linux字符驱动中实现一个加法应用
目的
熟悉linux字符驱动框架。
设计内核模块,通过对设备节点的读写实现加法操作的可视化输入输出。
先丢代码
/*
* dev_sub.c
*
* Author: licay <licay@xxx.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#define MEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define DEV_MAJOR 260
static int device_major = DEV_MAJOR;
module_param(device_major, int, S_IRUGO);
typedef struct {
struct cdev cdev;
unsigned char output[MEM_SIZE];
unsigned char input[MEM_SIZE];
} dev_sub_t;
dev_sub_t *dev_subp;
static int globalmem_open(struct inode *inode, struct file *filp)
{
filp->private_data = dev_subp;
return 0;
}
static int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long globalmem_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
dev_sub_t *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
memset(dev->input, 0, MEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
loff_t * ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
int a,b;
dev_sub_t *dev = filp->private_data;
if (sscanf(dev->input,"%d %d ",&a,&b) == 2)
sprintf(dev->output,"%d+%d=%d\n", a, b, a+b);
else
sprintf(dev->output,"input error\n");
/**/
if (p >= MEM_SIZE)
return 0;
if (count > MEM_SIZE - p)
count = MEM_SIZE - p;
if (copy_to_user(buf, dev->output + p, count)) {
ret = -EFAULT;
} else {
*ppos += count;
ret = count;
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}
return ret;
}
static ssize_t globalmem_write(struct file *filp, const char __user * buf,
size_t size, loff_t * ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
dev_sub_t *dev = filp->private_data;
if (p >= MEM_SIZE)
return 0;
if (count > MEM_SIZE - p)
count = MEM_SIZE - p;
if (copy_from_user(dev->input + p, buf, count))
ret = -EFAULT;
else {
*ppos += count;
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
}
return ret;
}
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0:
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > MEM_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1:
if ((filp->f_pos + offset) > MEM_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
static void globalmem_setup_cdev(dev_sub_t *dev, int index)
{
int err, devno = MKDEV(device_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}
static int __init globalmem_init(void)
{
int ret;
dev_t devno = MKDEV(device_major, 0);
/* request a device number */
if (device_major)
ret = register_chrdev_region(devno, 1, "dev_sub");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "dev_sub");
device_major = MAJOR(devno);
}
if (ret < 0)
return ret;
dev_subp = kzalloc(sizeof(dev_sub_t), GFP_KERNEL);
if (!dev_subp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalmem_setup_cdev(dev_subp, 0);
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(globalmem_init);
static void __exit globalmem_exit(void)
{
cdev_del(&dev_subp->cdev);
kfree(dev_subp);
unregister_chrdev_region(MKDEV(device_major, 0), 1);
}
module_exit(globalmem_exit);
MODULE_AUTHOR("licay <licay@xxx.com>");
MODULE_LICENSE("GPL v2");
编译内核模块
创建makefile文件如下
KVERS = $(shell uname -r)
# Kernel modules
obj-m += dev_sub.o
#obj-m += multi_dev_sub.o
# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
将dev_sub.c
和Makefile
放在同一目录下,直接运行命令make即可生成dev_sub.ko
内核模块。
sudo insmod dev_sub.ko #加载模块
lsmod #显示加载的模块
cat /proc/devices #查看设备号
#mknod 设备名 设备类型 主设备号 次设备号
mknod /dev/dev_sub c 260 0 #创建设备节点
将模块加载到内核后是无法直接访问的,需要创建设备节点与模块绑定,该模块在程序中设定设备号为260。因此直接使用mknod
命令创建节点/dev/dev_sub
。
使用明令对节点进行读写:
echo "22 33" > /dev/dev_sub && cat /dev/dev_sub