您的位置:首页 > 运维架构 > Linux

linux驱动学习笔记1(简单实现open,read,write,ioctl)

2017-08-02 18:33 645 查看
以前开发过程中用过无数次的ioctl函数,一直不知道其内部如何实现的,最近正好在看这方面的资料,并结合网上的代码做了实践,这里记录下。

首先编辑一个驱动模块,取名demo.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/cdev.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <linux/ctype.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include "demo.h"

MODULE_AUTHOR("Sunny");
MODULE_LICENSE("Dual BSD/GPL");

struct demo_dev *demo_devices;

static unsigned char demo_inc = 0;  //全局变量,每次只能打开一个设备

static u8 demo_buffer[256];

int demo_open(struct inode *inode, struct file *filp)
{
struct demo_dev *dev;

if (demo_inc > 0)
return -ERESTARTSYS;

demo_inc++;

dev = container_of(inode->i_cdev, struct demo_dev, cdev);
filp->private_data = dev;

printk("demo_open successfully\n");

return 0;
}

int demo_release(struct inode *inode, struct file *filp)
{
demo_inc--;
return 0;
}

ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int result;
loff_t pos = *f_pos; //pos: offset

if (pos >= 256){
result = 0;
goto out;
}
if (count > (256 - pos))
count = 256 - pos;
pos += count;

if (copy_to_user(buf, demo_buffer + *f_pos, count)){
count = -EFAULT;
goto out;
}

*f_pos = pos;

printk("demo_read successfully\n");

out:
return count;
}

ssize_t  demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
ssize_t retval = -ENOMEM;
loff_t pos = *f_pos;

if (pos > 256)
goto out;
if (count > (256 - pos))
count = 256 - pos;

pos += count;
if (copy_from_user(demo_buffer + *f_pos, buf, count)) {
retval = -EFAULT;
goto out;
}

*f_pos = pos;
retval = count;

printk("demo_write successfully\n");

out:
return retval;
}

long  demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (cmd == COMMAND1) {
printk("ioctl command 1 successfully\n");
return 0;
}
if (cmd == COMMAND2) {
printk("ioctl command 2 successfully\n");
return 0;
}

printk("demo_ioctl error\n");
return -EFAULT;
}

loff_t demo_llseek(struct file *filp, loff_t off, int whence)
{
loff_t pos;

pos = filp->f_pos;
switch (whence) {
case 0:
pos = off;
break;
case 1:
pos += off;
break;
case 2:
default:
return -EINVAL;
}

if ((pos > 256) || (pos < 0))
return -EINVAL;

printk("demo_llseek successfully\n");

return filp->f_pos = pos;
}

struct file_operations demo_fops = {
.owner = THIS_MODULE,
.llseek = demo_llseek,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
.open = demo_open,
.release = demo_release
};

void demo_cleanup_module(void)
{
dev_t devno = MKDEV(DEMO_MAJOR, DEMO_MINOR);

if (demo_devices) {
cdev_del(&demo_devices->cdev);
kfree(demo_devices);
}
unregister_chrdev_region(devno, 1);
}

/*
Init module流程:
1)注册设备号MKDEV;
2)注册设备驱动程序,即初始化cdev结构(嵌入到demo_devices结构中)
*/
int demo_init_module(void)
{
int result;
dev_t dev = 0;

dev = MKDEV(DEMO_MAJOR, DEMO_MINOR);
result = register_chrdev_region(dev, 1, "DEMO");
if (result < 0) {
printk(KERN_WARNING "DEMO: can't get major %d\n", DEMO_MAJOR);
return result;
}
demo_devices = kmalloc(sizeof(struct demo_dev), GFP_KERNEL);
if (!demo_devices) {
result = -ENOMEM;
goto fail;
}
memset(demo_devices, 0, sizeof(struct demo_dev));
cdev_init(&demo_devices->cdev, &demo_fops);
demo_devices->cdev.owner = THIS_MODULE;
demo_devices->cdev.ops = &demo_fops; //将创建的字符设备与file_operations中各函数操作连接起来

result = cdev_add(&demo_devices->cdev, dev, 1);
if (result) {
printk(KERN_NOTICE "error %d adding demo\n", result);
goto fail;
}
return 0;
fail:
demo_cleanup_module();
return result;
}

module_init(demo_init_module);
module_exit(demo_cleanup_module);
其中demo.h文件如下:

#ifndef _DEMO_H_
#define _DEMO_H_

#include <linux/ioctl.h>

/*Macros to help debuging*/
#undef PDEBUG
#ifdef DEMO_DEBUG
#ifdef __KERNEL__
#define PDEBUG(fmt, args...) printk(KERN_DEBUG "DEMO:" fmt,## args)
#else
#define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#endif
#else
#define PDEBUG(fmt, args...)
#endif

#define DEMO_MAJOR 224
#define DEMO_MINOR 0
#define COMMAND1 1
#define COMMAND2 2

struct demo_dev {
struct cdev cdev;
};

ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
loff_t demo_llseek(struct file *filp, loff_t off, int whence);
long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);

#endif


然后编辑一个Makefile,如下:

obj-m := demo.o            #要生成的模块名
modules-objs:= demo.o         #生成这个模块名所需要的目标文件

KDIR := /lib/modules/`uname -r`/build
PWD := $(shell pwd)

default:
make -C $(KDIR) M=$(PWD) modules

clean:
rm -rf *.o *.cmd *.ko *.mod.c *.order *.symvers
运行命令make,生成demo.ko,然后insmod demo.ko,接着创建设备节点mknod /dev/sunny c 224 0,之后便是创建应用测试程序进行验证了,如下:

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

#define COMMAND1 1
#define COMMAND2 2

int main(int argc, char **argv)
{
int fd;
int i;
char data[256] = {0};
int retval;
char *name = "/dev/sunny";

if(argc == 2)
{
name = argv[1];
}

fd = open(name, O_RDWR | O_CREAT);
if (fd == -1) {
perror("open error\n");
exit(-1);
}
printf("open %s successfully\n", name);

retval = write(fd, "yangjin", 7);
if (retval == -1) {
perror("write error\n");
close(fd);
exit(-1);
}
retval = lseek(fd, 0, 0);
if (retval == -1) {
perror("lseek error\n");
close(fd);
exit(-1);
}
retval = read(fd, data, 10);
if (retval == -1) {
perror("read error\n");
close(fd);
exit(-1);
}
printf("read successfully: %s\n", data);

retval = ioctl(fd, COMMAND1, 0);
if (retval == -1) {
perror("ioctl error\n");
close(fd);
exit(-1);
}
printf("ioctl command 1 successfully\n");

close(fd);
return 0;
}

至于原理,我觉得主要是创建的设备的主次设备号在驱动中被注册到文件系统中的缘故。

另外,实际测试中发现,linux中有默认的open函数,如果demo_fops定义如下,即去掉open,那么,上面的测试程序将会用linux默认的open函数打开。

struct file_operations demo_fops = {
.owner = THIS_MODULE,
.llseek = demo_llseek,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
//.open = demo_open,
.release = demo_release
};
或者,如果操作的不是/dev/sunny文件,那所有的打开/读/写等操作都是用的linux默认的那套函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: