1_字符设备驱动程序之LED驱动程序

一. 概念介绍
从上到下,一个软件系统可以分为:应用程序、库、内核、驱动程序。
这里写图片描述
(1)应用程序使用库提供的open函数打开代表LED的设备文件。
(2)库根据open函数传入的参数执行“swi”指令,这条指令会引发CPU异常,进入内核。
(3)内核的异常处理函数根据这些参数找到相应的驱动程序,返回一个文件句柄给库,进而返回给应用程序。
(4)应用程序得到文件句柄后,使用库提供的write或ioclt函数发出控制命令。
(5)库根据write或ioclt函数传入的参数执行“swi”指令,这条指令会引起CPU异常,进入内核。
(6)内核的异常处理函数根据这些参数调用驱动程序的相关函数,点亮LED。

这里写图片描述
open ,write 这些函数不是我们实现的,是C库实现的, C库也属于应用层。
当我们的应用程序调用open,write这些“系统调用”的时候,C库会进入到内核,内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。
C库怎么进入内核呢?
这些open ,write的实现,实际上是执行一条swi汇编指令,然后后面加入某个值,这条汇编指令就会引发一个异常,相当于中断一样,当发生异常的时候,就会进入内核的异常处理函数里面。
内核这一层“syatem lan interface 系统调用接口”:在异常处理函数里面根据发生中断的原因,调用不同的处理函数。比如我们用open的时候,传进来的值是val1,用write的时候,传进来的值是val2,内核里面的系统调用接口就根据传进来不同的值,去调用我们的sys_open,sys_write。
例如:我们有一个函数,
int main()
{
int fd1, fd2;
int val=1;
fd1 = open(“/dev/led”, O_RDWR); // fd1是打开led的设备文件,O_RDWR可读可写
write(fd1, &val, 4); //将&val的值写入设备文件”/dev/led”,应用程序里的write函数将参数传给驱动程序里的first_drv_write函数
fd2 = open(“hello.txt”, O_RDWR); //fd2是打开一个文本文件
write(fd2, &val, 4); //将&val所指向内存地址的值写入文本文件
}
函数里两个open函数(或两个write函数),引发了不一样的行为,第一个是操控硬件,第二个是读写文件。 谁来实现不一样的行为呢?内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。sys_open,sys_write最终会根据打开不同的文件找到不同的底层驱动程序,调用不同驱动程序里的open函数、write函数来实现这些功能。

二、字符设备驱动程序的框架
实现步骤:
1. 在Source Insight新建一个文件,这是我们的第一个驱动程序,命名first_drv.c,然后写出first_drv_open, first_drv_write函数
2. 定义file_operations结构体, 把驱动函数first_drv_open, first_drv_write填充到里面
3. 把这个结构告诉内核, 通过这个注册函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现
4. 谁来调用注册函数 ? 驱动的入口函数first_drv_init 来调用这个注册函数
5. 修饰一下这个入口函数module_init(first_drv_init)
第一个驱动的入口函数是first_drv_init,那第二个呢?有不同的入口函数,函数名称各有不同,
内核怎么知道去调用你这个函数?怎么知道你的入口函数是这一个?那得修饰一下入口函数。
怎么修饰? 是用这么一个宏来定义的 module_init(first_drv_init);
module_init定义一个结构体,结构体里面有一个函数指针,指向first_drv_init 这个入口函数。
当我们去加载或安装一个驱动程序的时候,内核就会自动去找到这么一个结构体module_init,然后调用里面的
函数指针,它就指向我们的入口函数first_drv_init。 我们的入口函数first_drv_init就把这个结构file_operations告诉内核。

first_drv.c

//第一步:驱动功能实现
static int first_drv_open(struct inode *inode, struct file *file)   //应用程序打开设备文件的时候就会进入到内核里面,
{
    printk("first_drv_open\n");                         //调用驱动程序里面的first_drv_open,然后打印"first_drv_open"
    return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    printk("first_drv_write\n");
    return 0;
}
//第二步:定义结构体,并把驱动函数填充进去
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,                           /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,            
    .write  =   first_drv_write,       
};

//第四步:实现驱动入口函数
int first_drv_init(void)    //入口函数调用注册函数
{
    //第三步:把结构体告诉内核
    register_chrdev(111, "first_drv", &first_drv_fops);  //注册,告诉内核,"first_drv"名字可随便写
    return 0;
}

void first_drv_exit(void)
{
    unregister_chrdev(111, "first_drv");   //卸载
    return 0;
}

//第五步:修饰入口函数及出口函数
module_init(first_drv_init);
module_exit(first_drv_exit);

firstdrvtest.c

#include <sys/types.h>     /*应用程序调用open, write函数,这两个函数是C库实现的*/
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    int fd;          
    int val = 1;
    fd = open("/dev/xxx", O_RDWR);   //open一个设备, xxx名字不重要
    if(fd<0)                         //如果不能打开文件,就打印"can't open!"
        printf("can't open!\n");
    write(fd, &val, 4);
    return 0;
}

三. 设置主设备号及创建设备节点
1、设置主设备号
我们设备驱动程序里面可以自动分配主设备号, 也可以由我们自己手工指定。

// 设置为 0 时是系统自动分配主设备号,通过cat /proc/device看一下系统为我们的first_drv分配的主设备号是多少。
major = register_chrdev(0, "first_drv", &first_drv_fops); 

// 手动分配主设备号111给first_drv
register_chrdev(111, "first_drv", &first_drv_fops); 

2、创建设备节点
当应用程序执行 open(“/dev/xyz”) 操作时,这个/dev/xyz是怎么来的 ?
2.1 手工建立

// 创建设备节点(在串口执行命令)
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号

//查看设备信息
ls -l /dev/xyz

2.2 自动创建
mdev – 根据系统信息创建设备节点

static struct class *firstdrv_class;               //一个类
static struct class_device  *firstdrv_class_dev;   //类下面再建立一个设备

int major;
static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops);   //主设备号可写为0,让系统自动给我们分配

    //创建设备信息,执行后会出现 /sys/class/firstdrv
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");    //创建一个类,类下面再创建一个设备

    //创建设备节点,就是根据上面的设备信息来的
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");  /* /dev/xyz */

    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv");   //卸载

    //删除节点及信息
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

对应的测试程序firstdrvtest.c也要修改:

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

int main(int argc, char **argv)
{
    int fd;      
    int val = 1;
    fd = open("/dev/xyz", O_RDWR);   //无论主设备号怎么变,/dev/xyz都是系统帮我们创建的
    if(fd<0)
        printf("can't open!\n");
    write(fd, &val, 4);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值