LDD3学习笔记(3):字符驱动1

1、scull( Simple Character Utility for Loading 

Localities)的设计

Scull是一个字符驱动,它操作一块内存区域,就好像它是一个设备,因此在以下的介绍中我们可以互换的使用设备和scull操作的内存区。

编写驱动的第一步是定义驱动将要提供给用户的功能。

模块实现的每种设备都被引用为一种类型。

Scull源码实现:

scull0到scull3四个设备,每个都由一个全局永久的内存区组成,全局表示如果设备被多次打开设备中含有的数据将由所有打开它的文件描述符所共享,永久表示如果设备关闭又重新打开,数据不会丢失。

Scullpipe0到scullpipe3四个FIFO(队列特性,先入先出)设备,行为像管道,一个进程读的内容来自另一个进程所写的,如果多个进程读同一个设备,它们将竞争数据。

Scullsingle、scullpriv、sculluid、scullwuid这些设备与scull0相似,但是在允许打开的时候有一些限制,scullsingle同一时刻(即一次)只允许一个进程使用驱动,scullpriv对每个虚拟终端是私有的,因为每个控制台/终端的进程有不同的内存区,sculluid和scullwuid可以多次打开,但是一次只能一个用户打开。

每个scull设备扮演了不同的驱动角色,并且难度不同。

2、主次编号

字符设备通过文件系统中的名字来存取,这些名字是特殊文件,即设备文件(节点),通常位于/dev目录下,在这个目录下,使用ls -l命令将会看到设备节点的信息,在想命令输出的第一列中以“c”标识的为字符设备,同理“b”标识的为块设备。

在使用ls -l命令的显示中,我们会看到以逗号分隔的两个数字,在最后修改日期的前面,这些数字就是设备节点的主次设备编号。

传统上,主编号标识与设备相关联的驱动,例如,/dev/null和/dev/zero都由驱动1来管理,而虚拟控制台和串口终端都由驱动4来管理,同样,vcs1和vcsa1设备都由驱动7管理,现在linux内核允许多个驱动共享主编号,但是通常大部分设备仍然是一个主编号对应一个驱动。

次编号被内核用来决定引用那个设备(即使用次编号在本地设备数组中查找,例如,两个功能相同的设备(比如两个磁盘),使用的驱动相同,所以设备节点文件中主次编号就起到了确定设备的作用)。

2.1、设备编号的内部表示

在内核中,dev_t类型用来持有设备编号,包括主次编号。2.6.0内核中,dev_t是32位的量,12位用作主编号,20位用作次编号。

获取一个dev_t的主次编号,使用宏:

MAJOR(dev_t dev);

MINOR(dev_t dev);

反之,如果你有主次编号,需要转换为一个dev_t可以使用:

MKDEV(int major , int minor);

2.2、分配和释放设备编号

在建立一个字符驱动时,你的驱动需要做的第一件事就是获取一个或多个设备编号来使用,为此需要的函数是:

Int register_chrdev_region(dev_t first , unsigned int count , char *name);

First是要分配的起始设备编号,first的次编号通常是0,count是请求的连续设备编号的总数。那么连接到这个编号范围的设备的名字,它会出现在/proc/devices和sysfs中。

分配成功函数返回0,失败返回一个负的错误码,这同大部分内核函数相同。这个函数需要你事先知道需要的主设备编号,但是通常你不会知道,这就需要动态分配主编号。具体函数为:

Int alloc_chrdev_region(dev_t* dev , unsigned int firstminor , unsigned int count , char *name);

函数中dev是一个只输出的参数,在函数成功后持有你分配范围的第一个数,firstminor应当是请求的第一个要用的次编号,通常是0,count和name同上一个函数一样。

无论怎么样分配设备编号,在不使用的时候都应该释放,设备编号释放使用的函数为:

Void unregister_chrdev_region(dev_first , unsigned int count);

调用这个函数的地方通常是模块的清理函数。

在用户程序使用设备之前,你的驱动需要连接到具体的设备操作函数上。

对于新驱动的主编号应该使用动态分配,也就是使用alloc_chrdev_region而不是register_chrdev_region

动态分配的缺点是无法提前串接设备节点,因为分配给你的模块的主编号会变化,但这些不影响驱动的正常使用,因为编号分配后可以从/proc/devices中读取。

为使用动态主编号来加载一个驱动,需要编写一个脚本代替insmod,脚本中在insmod之后,读取/proc/devices来创建/dev下面的设备节点文件。脚本如下:

#!/bin/sh

module="scull"    #重定义变量

device="scull"

mode="664"

# invoke insmod with all arguments we got

# and use a pathname, as newer modutils don't look in . by default

/sbin/insmod ./$module.ko $* || exit 1    #加载模块

# remove stale nodes

rm -f /dev/${device}[0-3]     #移除已有设备节点

major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #从/proc/devices文件中获取设备主编号

mknod /dev/${device}0 c $major 0   #创建设备节点

mknod /dev/${device}1 c $major 1

mknod /dev/${device}2 c $major 2

mknod /dev/${device}3 c $major 3

# give appropriate group/permissions, and change the group.

# Not all distributions have staff, some have "wheel" instead.

group="staff"

grep -q '^staff:' /etc/group || group="wheel"

chgrp $group /dev/${device}[0-3]     #改变设备的组和模式

chmod $mode /dev/${device}[0-3]

与加载脚本对应的是清理脚本,用来清理/dev目录并去除模块。

除此之外你还可以编写一个init脚本,它接受传统参数,如start stop restart 并且完成加载和卸载脚本的功能。

动态分配主编号的典型源码:

if (scull_major) {

 dev = MKDEV(scull_major, scull_minor);

 result = register_chrdev_region(dev, scull_nr_devs, "scull");

} else {

 result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");

 scull_major = MAJOR(dev);

}

if (result < 0) {

 printk(KERN_WARNING "scull: can't get major %d\n", scull_major);

 return result;

}

 

时间: 2024-08-12 05:50:26

LDD3学习笔记(3):字符驱动1的相关文章

LDD3学习笔记(6):字符驱动4

  1.快速参考 本章介绍了下面符号和头文件. struct file_operations 和 struct file 中的成员的列表这里不重复了. #include <linux/types.h> dev_t  dev_t 是用来在内核里代表设备号的类型. int MAJOR(dev_t dev); int MINOR(dev_t dev); 从设备编号中抽取主次编号的宏. dev_t MKDEV(unsigned int major, unsigned int minor); 从主次编号

LDD3学习笔记(4):字符驱动2

1.重要的数据结构 注册设备编号仅仅是驱动代码需要完成的任务之一,还有很多基础性的驱动操作需要驱动代码来完成,这里有3个重要的内核数据结构需要了解一下分别是:file_operations.file.inode. 1.1.文件操作 File_operation结构的功能是建立一个字符驱动与设备编号的连接.通常结构中的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作置为NULL. __user这种注解是一种文档形式,表明一个指针是一个不能被直接解除引用的用户空间地址,对于

LDD3学习笔记(5):字符驱动3

  1.字符设备注册 在运行时获得一个独立的cdev结构的代码: Struct cdev* my_cdev=cdev_alloc(); My_codev->ops=&my_fops; 将cdev结构嵌入自己设备特定的结构: Void cdev_init(struct cdev* cdev , struct file_operations* fops); Cdev结构建立后,告诉内核: Int cdev_add(struct cdev*dev ,dev_t num,unsigned int c

LDD3学习笔记(9):高级字符驱动操作

 1.ioctl接口 ioctl 驱动方法有和用户空间版本不同的原型: int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); 2.阻塞I/O 阻塞进程,使它进入睡眠直到请求可继续. 当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除,直到发生某些事情改变了那个状态.一个睡着的进程将不被任何CPU调度,因此不会运行,一个睡着的进程已被搁置到系

LDD3学习笔记(1):设备驱动简介

 1.几乎每个系统操作都被映射到具体的物理设备上,而任何设备的控制操作都由特定于要寻址的相关代码来进行,这些代码成为设备驱动. 2.linux可以在运行时扩展由内核提供的特性,既可以在系统运行时增加内核的功能(也可以删除). 3.每块可以在运行时添加到内核的代码称为一个模块,每个模块由目标代码组成(既功能的实现). 4.linux将设备分为三种基本类型:字符设备.块设备.网络设备.典型的字符设备如:/dev/tty1(串口),块设备如:/dev/sda(磁盘),网络设备如:eth0(网卡),在这

LDD3学习笔记(20):网络驱动

  #include <linux/netdevice.h> 定义 struct net_device 和 struct net_device_stats 的头文件, 包含了几个其他网络驱动需要的头 文件. struct net_device *alloc_netdev(int sizeof_priv, char *name, void (*setup)(struct net_device *); struct net_device *alloc_etherdev(int sizeof_pri

LDD3学习笔记(21):tty驱动

  #include <linux/tty_driver.h> 头文件, 包含 struct tty_driver 的定义和声明一些在这个结构中的不同的标志. #include <linux/tty.h> 头文件, 包含 tty_struct 结构的定义和几个不同的宏定义来易于存取 struct termios 的成员的单个值. 它还含有 tty 驱动核心的函数声明. #include <linux/tty_flip.h> 头文件, 包含几个 tty flip 缓冲内联

LDD3学习笔记(15):PCI驱动

 #include <linux/pci.h> 包含 PCI 寄存器的符号名和几个供应商和设备 ID 值的头文件. struct pci_dev; 表示内核中一个 PCI 设备的结构. struct pci_driver; 代表一个 PCI 驱动的结构. 所有的 PCI 驱动必须定义这个. struct pci_device_id; 描述这个驱动支持的 PCI 设备类型的结构. int pci_register_driver(struct pci_driver *drv); int pci_m

LDD3学习笔记(16):USB驱动

  #include <linux/usb.h> 所有和 USB 相关的头文件. 它必须被所有的 USB 设备驱动包含. struct usb_driver; 描述 USB 驱动的结构. struct usb_device_id; 描述这个驱动支持的 USB 设备的结构. int usb_register(struct usb_driver *d); 用来从USB核心注册和注销一个 USB 驱动的函数. struct usb_device *interface_to_usbdev(struct