linux字符驱动框架(编写流程)

一.字符驱动框架
1.应用层每个文件在内核层对应一个INODE号,存在在内核的struct iNode结构体(成员i_ino).
2.struct iNode结构体中存在一个struct *i_cdev结构指针,该指针指向字符设备驱动结构。
3.每个字符设备在内核中都对应一个cdev结构体,cdev通过device_id设备号来互相区分(i_rdev);
4.每个cdev对应不同的硬件设备,譬如led、串口等等.
5.inode号-->struct inode--->cdev指针--->设备号--->硬件
6.写1个字符驱动需要做的几个步骤:
   A.分配一个cdev对象
   B.填充cdev对象里的内容(提供fops业务层操作对象以及操作硬件实现函数,open/read/write等)
   C.分配设备号
   D.设备号和cdev对象关联(注册设备号)
7.应用层调用open等操作是如何关联到内核的fps->open的,关联系统调用号,使用swi 系统调用号进行系统调用.fps中每个函数都要添加系统调用号关联起来:
   A.../kernal/calls.S中按照顺序添加系统调用号,譬如CALL(sys_my_open);CALL(sys_my_read);系统调用号默认排序递增。
   B.../kernal/sys.c中添加函数SYSCALL_DEFINE1/2/3(my_open,xx,yy,cc),1/2/3表示系统调用参数的个数,my_open是函数名称,即SYSCALL_DEFINE后函数变成sys_my_oepn;
   C.测试:应用层程序调用syscall(系统调用号,参数1...);就能调用到内核.
   D.open函数系统调用到sys_open后,执行do_sys_open分配file文件得到fd,从inode结构体中拿到fops操作集,并使用fd_install(fd,f)。
8.内核驱动执行流程
  驱动执行的两种方法:内核启动初始化和insmod xx.ko初始化
  A.内核启动初始化
    module_init(my_test_init),将my_test_init函数编译进指定段(.initcall1/2/3/4/6.init段,123456为初始化优先级),每个段区分不同的初始化等级device_initcall(fn)。上电时从指定段中读出这些函数指针并初始化.
    系统启动时解析.initcall段并调用my_test_init函数。
    链接脚本vmlinux.lds,Image、zImage、uImage的区别,Image是原型,zImage是Image基础上做的压缩,uImage是在zImage的基础上加的64字节头.
    所以vmlinux.lds也对应三个.64字节头包括版本信息、文件大小、时间、load地址、入口地址、CRC信息.
    系统启动在./init/main.c中开始执行start_kernal
  B.insmode xx.ko初始化
    最终也是调用module_init(my_test_init)
    sudo strace insmod xxx.ko能看到调用过程.

二.编写字符驱动测试程序

1.编写mychardev.c

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>//file_operations结构体
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>

#define BUFFER_MAX    (10)
#define OK            (0)
#define ERROR         (-1)

struct cdev *gDev;//代表字符设备的数据结构
struct file_operations *gFile;
//struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带.该驱动程序的核心。它给出了对文件操作函数的定义。当然,具体的实现函数是留给驱动程序编写的
dev_t  devNum;//设备文件的设备号
unsigned int subDevNum = 1;
int reg_major  =  232;
int reg_minor =   0;
char *buffer;
int flag = 0;

struct class *my_class;


int hello_open(struct inode *p, struct file *f)
{
    printk(KERN_EMERG"hello_open\r\n");
    return 0;
}

ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
    printk(KERN_EMERG"hello_write\r\n");
    return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
    printk(KERN_EMERG"hello_read\r\n");
    return 0;
}
int hello_init(void)//三件事情注册驱动,申请资源,节点创建
{

    devNum = MKDEV(reg_major, reg_minor);//将主设备号和次设备号转换成dev_t类型
    if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){
        printk(KERN_EMERG"register_chrdev_region ok \n");
    }else {
    printk(KERN_EMERG"register_chrdev_region error n");
        return ERROR;
    }
    printk(KERN_EMERG" hello driver init \n");
    gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
    gFile->open = hello_open;
    gFile->read = hello_read;
    gFile->write = hello_write;
    gFile->owner = THIS_MODULE;
    cdev_init(gDev, gFile);//初始化,建立cdev和file_operation 之间的连接
    cdev_add(gDev, devNum, 3);//把它添加到系统中去,注册设备,通常发生在驱动模块的加载函数中

  /* create your own class under /sysfs */
      my_class = class_create(THIS_MODULE, "hello");
      if(IS_ERR(my_class))
      {
           printk("Err: failed in creating class.\n");
           return -1;
       }

  /* register your own device in sysfs, and this will cause udev to create corresponding device node */
      device_create(my_class, NULL, devNum, NULL,"hello");


    return 0;
}

void __exit hello_exit(void)
{
    cdev_del(gDev);
    device_destroy(my_class, devNum);         //delete device node under /dev
    class_destroy(my_class);                               //delete class created by us

    unregister_chrdev_region(devNum, subDevNum);
    kfree(gDev);
    kfree(gFile);
    return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

编写Makefile:

obj-m += mychardev.o

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

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

clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean

编译:

编写app_test.c测试程序:

#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    char buf[1024];


    fd = open("/dev/hello", O_RDWR);
    if (fd == -1) {
        printf("无法打开设备文件\n");
        return -1;
    }


    if (read(fd, buf, sizeof(buf)) == -1) {
        printf("读取数据失败\n");
        close(fd);
        return -1;
    }
    printf("从设备中读取的数据:%s\n", buf);


    char write_data[] = "Hello, driver!";
    if (write(fd, write_data, sizeof(write_data)) == -1) {
        printf("写入数据失败\n");
        close(fd);
        return -1;
    }


    close(fd);

    return 0;
}

dmesg:

测试结束,具体涉及硬件驱动时需要把ops里面的几个操作函数根据寄存器手册来实现.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值