【记录】初识字符设备驱动程序

本文介绍了一个简单的嵌入式驱动开发流程,包括编写read和write函数、定义file_operations结构体、注册和卸载字符设备驱动等步骤。并通过一个点亮LED灯的实际案例,详细展示了从驱动编写到应用程序调用的具体过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概要

u-boot:启动内核
内核:启动应用程序
应用程序:读写文件,点灯,获取按键值等等。

1.1应用程序与内核、驱动的关系

大家各司其职,显然应用程序不应该关心内部实现方式,使用标准的接口open, read, write等操作文件和设备(设备也是一种文件)。

以一个简单的应用程序为例。

int main()
{
    int fd1, fd2;
    int val = 1;

    fd1 = open("/dev/led", O_RDWR);
    write(fd1, &val, 4);

    fd2 = open("hello.txt", O_RDWR);
    write(fd2, &val, 4);
}

当应用程序调用C库中open,read,write等函数时,会通过汇编指令swi val引发异常,异常处理函数根据发生中断的原因调用不同的处理函数,sys_open, sys_read, sys_write即VFS(virtual file system)层。

这里写图片描述

1.2框架

1.写read,write函数
2.定义file_operations结构,填充内容
3.把结构告诉内核register_chrdev
4.驱动入口函数first_drv_init
5.使用宏修饰入口函数module_init(first_drv_init)

在VFS中,有一个大数组,每个成员代表一个file_operations结构,写好驱动后,将驱动注册到数组中。

2.第一个驱动程序:点灯

写好第一个驱动程序。

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>

static int first_drv_open(struct inode *inode, struct file *file)
{
    printk("first_drv_open\n");
    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,       
};

static int first_drv_init(void)
{
    register_chrdev(111, "first_drv", &first_drv_fops); // 注册驱动

    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(111, "first_drv"); // 卸载驱动
}

module_init(first_drv_init);
module_exit(first_drv_exit);

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   += first_drv.o

编译

book@book-desktop:/work/my_drivers/first_drv$ make
make -C /work/system/linux-2.6.22.6 M=`pwd` modules 
make[1]: Entering directory `/work/system/linux-2.6.22.6'
  CC [M]  /work/my_drivers/first_drv/first_drv.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /work/my_drivers/first_drv/first_drv.ko
make[1]: Leaving directory `/work/system/linux-2.6.22.6'

拷贝first_drv.ko到NFS文件系统中

book@book-desktop:/work/my_drivers/first_drv$ cp first_drv.ko /work/nfs_root/first_fs

开发板就可以看到first_drv.ko文件了。

# ls
bin           first_drv.ko  lib           sbin
dev           hello         linuxrc       sys
etc           hello.c       proc          usr
# 

开发板注册驱动程序,有个警告,暂时先忽略。

# insmod first_drv.ko 
first_drv: module license 'unspecified' taints kernel.

查看设备,已经有了我们写好的111驱动了。

# cat /proc/devices 
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
111 first_drv
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
# 

测试程序,其中名称可以任意

#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/xxx", O_RDWR);
    if(fd < 0)
    {
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

编译,并拷贝到NFS文件系统中

book@book-desktop:/work/my_drivers/first_drv$ arm-linux-gcc -o firstdrvtest firstdrvtest.c 
book@book-desktop:/work/my_drivers/first_drv$ cp firstdrvtest /work/nfs_root/first_fs

开发板上执行测试程序

# ./firstdrvtest 
can't open!

无法打开,因为还没有该设备的节点。创建节点,包含名称,类型,主设备号,次备号,重新执行,OK。

# mknod /dev/xxx c 111 0
# 
# ./firstdrvtest 
first_drv_open
first_drv_write
# 

3. 完善第一个驱动程序

注册驱动的时候,如果主设备号设置为0,那么系统会自动分配主设备号

major = register_chrdev(0, “first_drv”, &first_drv_fops);

由于主设备号已经变更,需要卸载,重新安装驱动程序

# lsmod
Module                  Size  Used by    Tainted: P  
first_drv               1728  0 
# rmmod first_drv
# lsmod
Module                  Size  Used by    Tainted: P  
#
# rm /dev/xxx
# mknod /dev/xxx c 252 0 
# ls -l /dev/xxx
crw-r--r--    1 0        0        252,   0 Jan  1 02:26 /dev/xxx
# ./firstdrvtest 
first_drv_open
first_drv_write
# 

这种方法,每次都要卸载,重新安装,不方便。

自动创建

每次创建一个设备都会在系统目录下增加设备信息。

# ls /sys/devices/
platform  system
# 

mdev机制,根据系统信息,自动创建设备节点。

重写驱动的入口和出口函数

static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册驱动

    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);
}

这样,就可以自动创建/dev/xyz设备节点。重新编译。
开发板上卸载驱动,重新加载驱动,查看设备节点。

# lsmod
Module                  Size  Used by    Tainted: P  
first_drv               1796  0 
# rmmod first_drv
# lsmod
Module                  Size  Used by    Tainted: P  
# insmod ./first_drv.ko
# lsmod
Module                  Size  Used by    Tainted: P  
first_drv               2156  0 
# ls -l /dev/xyz
crw-rw----    1 0        0        252,   0 Jan  1 02:47 /dev/xyz

如何产生主设备号的?其实,在加载过程中,由于使用了class_create和class_device_create,在系统目录下生成了该设备节点信息。

# cd /sys/
# ls
block     class     firmware  kernel    power
bus       devices   fs        module
# cd class/
# ls
firstdrv      mem           ppdev         scsi_host     usb_host
graphics      misc          printer       sound         vc
hwmon         mmc_host      rtc           spi_master    vtconsole
i2c-adapter   mtd           scsi_device   tty
input         net           scsi_disk     usb_endpoint
# cd firstdrv/
# ls
xyz
# cd xyz/
# ls
dev        subsystem  uevent
# cat dev
252:0
# 

在脚本文件中,定义支持热插拔。内核一旦有驱动加载或下载时,/proc/sys/kernel/hotplug这个程序会自动加载,卸载设备。

# vi /etc/init.d/rcS
#mount -t proc none /proc
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

这样,应用程序只需要知道设备名称就可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值