驱动程序开发:旧字符设备驱动程序

驱动程序开发:旧字符设备驱动程序

驱动程序:chrdevbase.c

/*
 *	根据linux内核的程序查找所使用函数的对应头文件。
 */
#include <linux/module.h>	// MODULE_LICENSE,MODULE_AUTHOR
#include <linux/init.h>		// module_init,module_exit
#include <linux/kernel.h>	// printk
#include <linux/fs.h>		// struct file_operations
#include <linux/uaccess.h>	// copy_to_user,copy_from_user

/* 
 * 为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分。
 * 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
 * 注意:旧字符设备驱动在使用主设备号时会将其对应下的所有次设备号使用掉,
 * 因此不存在哪一类(主设备号)驱动程序的哪一个(次设备号)驱动程序。
 */
#define CHRDEVBASE_MAJOR		200				//主设备号
#define CHRDEVBASE_NAME			"chrdevbase"	//设备名称

static char readbuf[100];						//读缓冲
static char writebuf[100];						//写缓冲
static char kerneldata[] = {"kernel data!"};

/*
 *	基本的字符设备驱动函数需要实现最基本的open、release、read、write设备操作函数。
 *	具体编写可以通过linux内核的程序进行参考。
 */
/* 设备打开函数 */
static int chrdevbase_open(struct inode *inode, struct file *filp) {
	// printk("chrdevbase_open\r\n");
	return 0;
}
/* 设备关闭函数 */
static int chrdevbase_release(struct inode *inode, struct file *filp) {
	// printk("chrdevbase_release\r\n");
	return 0;	
}
/* 从设备读取数据 */
static ssize_t chrdevbase_read(struct file *file, char __user *buf, size_t count, loff_t * ppos) {
	int ret = 0;
	// printk("chrdevbase_read\r\n");
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	/* 这里是将驱动程序的输入copy发送到应用程序buf中,从而让应用程序读取驱动程序的数据 */
	ret = copy_to_user(buf, readbuf, count);	// 将readbuf数据读取存入buf中,字节长度有用户count定义
	if(ret == 0) {
	} else{
	}
	
	return 0;	
}
/* 向设备写入数据 */
static ssize_t chrdevbase_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) {
	int ret = 0;
	// printk("chrdevbase_write\r\n");
	/* 这里是将应用程序的buf数据copy到应用程序writebuf中 */
	ret = copy_from_user(writebuf, buf, count);
	if(ret == 0) {
		printk("kernel receivedata %s\r\n",writebuf);
	} else{
	}
	
	return 0;
}

/*
 *	字符设备的操作集合:是将上面的具体设备操作函数进行初始化。
 *	owner:拥有该结构体的模块的指针,这里指向当前驱动模块。
 *  open、release、read、write是指向上面已经实现的设备操作函数。
 */
static const struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,
	.open = chrdevbase_open,
	.release = chrdevbase_release,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
};

/*
 *	因为这里需要将驱动编译成模块(Linux 下模块扩展名为.ko),这样的话修改驱动以后只需要编译一下驱动代码即可,
 *  不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。
 *	这里需要编写驱动加载函数和驱动卸载函数,并且需要将这驱动加载函数和驱动卸载函数进行注册。
 *	对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。
 */
/* 驱动入口(加载)函数 */
static int __init chrdevbase_init(void)
{
	int ret;	//保存调用函数返回值的临时变量

	printk("chrdevbase_init\r\n");

	/* 注册字符设备:设备号、设备名、结构体 file_operations 类型指针,指向设备的操作函数集合变量 */
	ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(ret < 0) {
		printk("chrdevbase_init fault!\r\n");
	}
	return 0;
}

/* 驱动出口(卸载)函数 */
static void __exit chrdevbase_exit(void)
{
	printk("chrdevbase_exit\r\n");

	/* 注销字符设备 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}

/*
 *  向Linux内核注册模块入口与出口函数,分别对应在终端输入“ismod或modprobe”和“rmmod”
 *  ismod不能解决模块的依赖关系,modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中。
 */
module_init(chrdevbase_init);  //入口
module_exit(chrdevbase_exit);  //出口

/* 模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("DJW 1411471554@qq.com");	// 这是作者信息

/*
 *	printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,
 *	这 8 个消息级别定义在文件 include/linux/kern_levels.h 里面。
 *	默认消息级别为 4。
 */

应用程序:chrdevbaseAPP.c

/*
 *	头文件可以通过linux手册查找,man no <xxx>
 *	1是普通的命令
 *	2是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件)
 *	3是库函数,如printf,fread
 *	4是特殊文件,也就是/dev下的各种设备文件
 *	5是指文件的格式,比如passwd, 就会说明这个文件中各个字段的含义
 *	6是给游戏留的,由各个游戏自己定义
 *	7是附件还有一些变量,比如向environ这种全局变量在这里就有说明
 *	8是系统管理用的命令,这些命令只能由root使用,如ifconfig
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 * argc:应用程序参数个数 
 * argv[]:具体打参数内容,字符串形式 
 * ./chrdevbaseAPP <filename> <1:2> 1为读,2为写
 * ./chrdevbaseAPP /dev/chrdevbase 1   表示从驱动里面读数据
 * ./chrdevbaseAPP /dev/chrdevbase 2   表示向驱动里面写数据
 */
int main(int argc, char *argv[])
{
    int ret;	//保存调用函数返回值的临时变量
    int fd;		//保存调用文件操作函数的文件描述符的临时变量
    char *filename;
    char readbuf[100],writebuf[100];
    static char usrdata[] = {"usr data!"};

	/* 判断输入的参数个数是否是3个 */
    if(argc != 3)
    {
        printf("Error usage!\r\n");
        return -1;
    }

/*
 *	写测试 APP 就是编写 Linux 应用,需要用到 C 库里面和文件操作有关的一些函数,比如
 *	open、 read、 write 和 close 这四个函数。
 */
    filename = argv[1];		//获取输入的第二个参数(驱动文件的绝对路径)
    fd = open(filename, O_RDWR);	//以读写方式打开驱动文件
    if(fd < 0) {
        printf("can't open file %s\r\n",filename);
        return -1;
    }
	//读操作
    if(atoi(argv[2]) == 1) {
        /*read读操作,这里通过调用应用程序read函数来调用驱动程序的读取函数,从而读取驱动程序的数据*/
        ret = read(fd, readbuf, 50);
        if(ret < 0) {
            printf("read file %s failed!\r\n",filename);
        } else {
            printf("APP read data:%s\r\n",readbuf);
        }
    }
    //写操作
    if(atoi(argv[2]) == 2) {
        memcpy(writebuf, usrdata, sizeof(usrdata));
		/*write写操作,这里通过调用应用程序write函数来调用驱动程序的写入函数,从而使数据写入驱动程序*/
        ret = write(fd, writebuf, 50);
        if(ret < 0) {
            printf("write file %s failed!\r\n",filename);
        } else {

        }
    }

    /*close关闭操作*/
    ret = close(fd);
    if(ret < 0) {
        printf("close file %s failed!\r\n",filename);
    }

    return 0;
}

步骤:
1、将驱动程序进行make操作,编译生成xxx.ko文件
2、使用交叉编译器将应用程序编译成可执行文件,如:我这里使用 arm-linux-gnueabihf-gcc xxxAPP.c -o xxxAPP
3、将xxx.ko和xxxAPP两个文件拷贝到存放驱动模块的目录中。
4、打开开发板并使用Linux系统选择通过TFTP从网络启动和使用NFS挂载网络根文件系统。
5、在PC机的串口终端中先输入depmod,后输入modprobe xxx.ko加载驱动文件,最后输入lsmod查看当前系统中存在的模块。
6、在PC机的串口终端中输入cat /proc/devices查看当前系统的所有设备的对应的设备号及名称。
7、驱动加载成功需要在/dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。
在PC机的串口终端中输入mknod /dev/xxx c 200 0,其中“mknod”是创建节点命令,“/dev/xxx”是要创建的节点文件,“c”表示这是个
字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。可以通过ls /dev/xxx -l查看该文件详情。
8、在PC机的串口终端中输入./xxxApp /dev/xxx 1或./xxxApp /dev/xxx 2指令来使用应用程序对驱动程序进行读写等操作。
9、在PC机的串口终端中输入rmmod xxx.ko卸载驱动模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值