Linux内核模块初探

相比较于裸机开发:驱动有以下的优势:

        1、抽象

        2、接口统一

        3、海量的扩展包SDK

        4、海量的协议栈

        5、内存管理

        6、进程调度

一、常见命令:

        EXPORT_SYMBOL(xxx);//将此内容开发给其他文件。在内核中,不用static修饰的代码也不会被其他文件使用(因为安全性考虑)

        modprobe 模块探针,找到该模块缺失的模块,自动查找依赖的模块(其在lib/modules/3.14.0里面找)

                第一步:拷贝 所有的ko到lib/modules/3.14.0

                第二部:运行depmod生成依赖文件

                第三步:使用modprobe <不带ko后缀>运行

二、Linux字符设备框架

        应用层的软件通过open打开内核中的设备函数文件,那么可以类似cpp中的虚函数的思想,使用一个多态的形式。那么c就是通过函数指针来回调这些函数。

        通过链表,链表存储函数指针,那么当open后,就在链表中找设备,在设备中找到真正所需要的函数,若有新设备,也可以通过链表添加。

        在链表中找设备如果通过字符来查找的话,会非常慢,为了提高效率,我们可以通过将设定设备号码。提高查找效率。

        但人常对字符敏感,计算机对数字敏感,因此,我们可以折中:给数字取一个别名:mknode 命令。

                                        mknod [选项]... 名称 类型 主设备号 次设备号

                                        

类型:设备类型,可以是b、c或p。其中b表示块设备,c表示字符设备,p表示命名管道(FIFO)

mknode led c 253 0

 - led:这是你要创建的设备文件的名称。
- c:这表示你要创建的是一个字符设备文件。如果你要创建一个块设备文件,你应该使用b。
- 253:这是设备的主设备号。这个数字应该与你在驱动程序中注册的设备号相匹配。在你的驱动程序中,你使用alloc_chrdev_region函数分配了一个设备号,这个函数会返回一个主设备号和一个次设备号。你应该使用这个主设备号来创建设备文件。
- 0:这是设备的次设备号。在大多数情况下,你可以将这个数字设置为0。

所以,mknod led c 253 0命令会创建一个名为led的字符设备文件,此文件通过设备号将应用层和内核连接起来(这里也体现了Linux的一切皆文件的思想。),其主设备号为253,次设备号为0。这个设备文件将与你的驱动程序关联,因此当你通过这个设备文件访问设备时,你的驱动程序将被调用

       

 字符设备有两个数字来描述。目的是在查询过程中,直接使用链表查询设备十分缓慢。因此需要设计一个效率较快的查找方法。这里就使用到了hash表。

       (1)数据结构:

                前面我们已经知道通过链表来查找字符设备,那么我们可以通过一个hash表来存储不同的链表,再在这个链表使用另外一个键来查找到具体的某个设备。

        这样就使用到两个数字。

那么对于这个表,就要插入设备,删除设备。即设备的注册,设备的注销。

(2)编程流程

即设备是:open->昵称文件->通过设备号关联内核中的函数->调用cdev_init中的open,realse函数->退出->删除驱动,删除占用的设备号

        1、申请设备号

alloc_chrdev_region (&devnum, 0, 1, "led");


        1. dev_t *dev: 这是一个指向 dev_t 类型变量的指针,函数会将分配的设备号存储在这个变量中。(这里也是函数常用的传参方式,用地址,往地址内写入值更方便)

        2. unsigned int firstminor: 这是请求的第一个次设备号。在大多数情况下,这个值应该是 0。

        3. unsigned int count: 这是请求的设备数量。这个值表示你想要分配多少个连续的设备号。

        4. const char *name: 这是设备的名称。这个名称将出现在 /proc/devices 和 sysfs 中。

在你的代码中,alloc_chrdev_region (&devnum, 0, 1, "led"); 这行代码的意思是请求分配一个字符设备号,第一个次设备号是 0,设备数量是 1,设备的名称是 "led"。
 

申请完设备号,使用完后应该删除,不然下次申请会浪费资源。

同理,设备驱动也需要删除

unregister_chrdev_region(devnum,1);//注销设备号
cdev_del(mycdev);

        1. cdev_del(mycdev);:这个函数用于删除一个字符设备。在驱动程序中,使用cdev_add函数注册了一个字符设备,这个设备关联了设备驱动程序和一个设备号。当不再需要这个字符设备时(例如,在卸载驱动程序时),应该使用cdev_del函数来删除它。

        2. unregister_chrdev_region(devnum,1);:这个函数用于释放一个设备号区域。在你的驱动程序中,使用alloc_chrdev_region函数分配了一个设备号区域。当不再需要这个设备号区域时(例如,在卸载驱动程序时),应该使用unregister_chrdev_region函数来释放它。


如果初始化成功,cdev结构体将与fops中的操作关联起来。然后,可以使用cdev_add将设备添加到系统中

        2、创建cdev结构体

    mycdev = cdev_alloc();

        3、绑定cdev和自己的设备驱动函数

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

 - struct cdev *cdev:这是一个指向字符设备结构体的指针。字符设备结构体代表了字符设备驱动程序中的设备。这个结构体需要在调用cdev_init之前分配和初始化struct cdev *mycdev = cdev_alloc();)。

const struct file_operations *fops:这是一个指向文件操作结构体的指针。文件操作结构体定义了设备驱动程序如何响应用户空间程序对设备文件的各种操作。例如,读设备文件、写设备文件、打开设备文件等。这个结构体通常在驱动程序中静态定义:

const struct file_operations myops = {
    .open = led_on,
    .close = led_off
}

 其实这里的cdev_init很像c++中的虚函数,在运行的时候根据结构体里面的内容决定具体执行的函数,在c语言中就是回调

在调用cdev_init时传入。

        4、插入linux字符设备框架(插入到链表)

 cdev_add(mycdev, devnum, 1);    //,表示你要添加的设备的数量。只添加了一个设备,所以这个参数的值是 1

(3)代码

        应用层:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main()
{
    while(1)
    {
        int fd = open ("led",O_RDWR);//申请文件的访问
        if(-1==fd){
            perror("open led");
            return -1;
        }
        printf("LED_ON\n");
        sleep(1);
        close(fd);//释放文件的访问
        printf("LED_OFF\n");
        sleep(1);
    }
    return 0;
}

内核驱动:

        

#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE ("Dual BSD/GPL");
MODULE_AUTHOR("JIN");
MODULE_DESCRIPTION("TEST");

int *gpiocon2 ;
    //int *GPIODA = GPIOCON + 1;//int 是一个指针,其在32位情况下占4字节所以直接是加4,和0x110000c44一样
int *gpiodat2 ;
int *gpiodat3 ;
int *gpiocon3 ;

int led_on(struct inode *i, struct file *f)//内核规定的调用函数的模板
{
    gpiocon2 = ioremap(0x11000c40,8);//物理地址转换到虚拟地址
    *gpiocon2 &= ~(0xf << 28);
    *gpiocon2 |= 0x1 << 28;
    gpiodat2 = gpiocon2+1;

    gpiocon3 = ioremap(0x114001E0,8);
    *gpiocon3 &= ~(0xf << 16);
    *gpiocon3 |= 0x1 << 16;
    gpiodat3 = gpiocon3+1;

    //设置高电平
    *gpiodat2 |= 0x1<<7;

    *gpiodat3 |= 0x1<<4;
    return 0;
}

int led_off(struct inode *i, struct file *f)
{
    *gpiodat2 &= ~(0x1<<7);
    *gpiodat3 &= ~(0x1<<4);
    return 0;
}
const struct file_operations myops = {
    .open = led_on,//类似一个回调函数
    .release = led_off
};

/*
void cdev_init(struct cdev *, const struct file_operations *);

struct cdev *cdev_alloc(void);

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);

void cdev_del(struct cdev *);

void cd_forget(struct inode *);
*/
dev_t devnum;
struct cdev *mycdev;
static int hello_init(void)
{
//1、申请设备号
//定义一个设备号,通过申请设备号的函数来传入设备号

    alloc_chrdev_region (&devnum, 0, 1, "led");
    printk("%d,%d\n",MAJOR(devnum),MINOR(devnum));
//2、创建cdev结构体
    mycdev = cdev_alloc();
//3、绑定cdev和自己的设备驱动函数
    cdev_init(mycdev, &myops);
//4、插入linux字符设备框架(插入到链表)

    cdev_add(mycdev, devnum, 1);    //,表示你要添加的设备的数量。在这个例子中,你只添加了一个设备,所以这个参数的值是 1
    printk ("hello knernal");
    return 0;
}

static void hello_exit(void)
{
    // iounmap(gpiocon2);
    // iounmap(gpiocon3);
    cdev_del(mycdev);
    unregister_chrdev_region(devnum,1);//注销设备号
    printk ("goodbuy kernal");
}

module_init(hello_init);

module_exit(hello_exit);

Makefile文件:

obj-m := led_driver.o

KERNEL_DIR = "/home/hqyj/linux-3.14"


PWD = $(shell pwd)
default:
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
	arm-linux-gcc led_app.c
	cp *.ko a.out  /home/hqyj/nfs/rootfs/

代码还不完美,请看下一篇文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值