10.重写最简单的驱动——打印驱动

1.模仿参考

新建myled.c,参考Myleds.c,复制他的一些函数代码修改函数名(Myleds.c是我之前自己写的,没有的话按照下面图片写open和write函数就行)

如:

2.定义结构体file_operations

3.把结构体告诉内核

我们定义了这个结构体,但是没有用起来,所以要告诉内核,怎么告诉,需要一个系统函数register_chrdev

原型:int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

参数1:主设备号

参数2:设备名

参数3:file_operations结构体

如:

4.驱动的入口函数

那么我们用register_chrdev来注册,register_chrdev被谁调用呢,被入口函数调用,怎么写入口函数

我们想,内核中可能有很多驱动入口函数,如seconddrv,thirddrv等,内核怎么分辨这些入口函数

***就要修饰一下,用宏module_init

module_init(入口函数名);

什么是module_init

当模块被加载时,执行moudle_init函数,该函数会调用初始化函数  

module_init是定义一个结构体,这个结构体里面有一个函数指针,指向这个入口地址,当我们加载一个驱动,或者安装一个驱动时,内核就会自动去找到这么一个结构体,去指向这个入口函数,然后入口函数就用register_chrdev注册,告诉内核这个file_operations结构体。

5.看 register_chrdev

我们研究register_chrdev这个函数,major是主设备号,当我们在终端上输入:ls  /dev  -l 时,可以看到c是指字符设备,7是主设备号,0-...是次设备号

主设备就是为了让应用程序可以找到file_operationsd的open成员的标识

如,对于字符设备驱动:

app:  open("/dev/xxx",...);

/dev/xxx有上面图片那种属性:类似crw-rw....,major(主),mior(次)

app的open函数打开/dev/xxx,就是通过主次设备号去找到register_chrdev中的file_operations的open函数,进行上层到底层的操作

register_chrdev是通过在内存里用一个数组,以major为索引,把file_operations挂接上去

6.具体流程:

7.完善驱动程序

(1)有入口函数,也有出口函数,参考Myleds.c

(2)我们只是简答写一个驱动程序,打印即可,内核打印是用printk

(2)包含头文件,参考Myleds.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/uaccess.h>

#include <asm/irq.h>

#include <asm/io.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

9.用ftp传到虚拟机上,编写Makefile

KERN_DIR = /work/system/linux-2.6.22.6         //内核目录



all:

        make -C $(KERN_DIR) M=`pwd` modules   



clean:

        make -C $(KERN_DIR) M=`pwd` modules clean

        rm -rf modules.order


obj-m   += myled.o //依赖文件

执行make后生成myled.ko文件

10.代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int firstdrv_led_open(struct inode *inode, struct file *file)
{
	printk("firstdrv_led_open\n");
	return 0;
}
static ssize_t firstdrv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("firstdrv_led_write\n");
	return 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations firstdrv_led_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   firstdrv_led_open,    	   
    .write	=	firstdrv_led_write,	   
};
int major;
int firstdrv_led_init(void)
{
	major =register_chrdev(0,"myled",&firstdrv_led_fops);//注册设备myled,告诉内核,注冊设备时给设备号写0,则内核会自己主动分配一个主设备号返回。
	
	return 0;
}
void firstdrv_led_exit(void)
{
	unregister_chrdev(major,"first_drv");//卸载
}
module_init(firstdrv_led_init);

module_exit(firstdrv_led_exit);
MODULE_LICENSE("GPL");

11.测试

编写测试程序mylec_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("/dev/myled", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	write(fd, &val, 4);
	return 0;
}

在虚拟机上用交叉编译工具生成可执行文件

#arm-linux-gcc -o mylec_test mylec_test.c

把mylec.ko 和mylec_test拷贝到络文件系统

在开发版终端上执行

发现不能输出打印信息,也没有/dev/myled的设备文件。原因是我们没有设置设备节点,有分手动设置和自动设置。这里我们先讲用mknod手动分配设备节点

我们所写的代码只是注册了一个名为myled的驱动,并通过register_chrdev让内核帮我们分配主设备号,可以使用                    cat /proc/devices 查看内核以后主设备号

发现内核帮我们把设备号设置为252,根据这个使用mknod手动分配设备节点

在开发版的终端上用 mknod /dev/xxx c 主设备号  次设备号(删除设备号为 rm  /dev/xxx)

这个时候就可以执行可执行文件了,成功打印

这就是最简单的驱动程序了,属于手动分配设备节点。下一节讲使用udev自动分配设备节点,使用

class_create为该设备创建一个class,再为每个设备调用 device_create创建对应的设备。

加载模块后,会自动在/dev/下创建myled设备文件。

发布了114 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_40535588/article/details/90576343