目录
基于宋宝华老师的《Linux设备驱动开发详解》
Linux文件操作
- 使用
creat
创建文件时,文件的存取权限要与umask
一起决定int umask(int newmask);
:只影响读写和执行权限
DOS
、Windows
系统中对于二进制文件和文本文件是有区分的,但Linux不区分二进制文件和文本文件
Linux文件系统
/bin
:包含基本命令,例如ls
/sbin
:包含系统命令,大多数是涉及系统管理的命令/dev
:设备文件存储目录,应用程序通过对这些文件的读写和控制以访问实际的设备/etc
:系统配置文件的所在地,一些服务器的配置文件也在这里。如用户账户和密码/lib
:系统库文件存放目录/mnt
:一般用于存放挂载储存设备的挂载目录,如cdrom
等目录/opt
:可选,有些软件包会被安装在这里/proc
:操作系统运行时,进程及内核信息存放在这里。该目录为伪文件系统proc
的挂载目录/tmp
:存放临时文件/usr
:系统存放程序的目录,如用户命令/var
:存放经常变化的内容,如其下的/log
就用来存放系统日志/sys
:2.6以后的内核所支持的sysfs
文件系统被映射在此目录上。Linux设备驱动模型中的总线、驱动和设备都可以在sysfs
文件系统中找到对应的节点。当内核检测到在系统中出现了新设备后,内核会在sysfs
文件系统中为该新设备生成一项新的记录
Linux文件系统与设备驱动
块设备访问方法
- 不通过文件系统直接访问裸设备,即通过Linux内核实现的
def_blk_fops
,即运行类似于dd if=/dev/sdb1 of=sdb1.img
时,内核走的是该实现
- 通过文件系统来访问块设备,即通过通常的
open
等访问- 因为文件系统会通过VFS统一的进行转化,不管访问的是
ext2
还是fat
等文件系统,应用层都只需要通过open
、read
等访问 - 因为在VFS中会自动的将其进行转化,类似于Java中的接口
- 因为文件系统会通过VFS统一的进行转化,不管访问的是
file结构体
- 当打开文件后,由内核自动创建
- 当关闭文件后,由内核自动释放
- 通常被命名为
file
或filep:file pointer
- 设备驱动通常关心如下
f_mode
:文件读/写模式f_flags
:文件标志,即是否阻塞式private_data
:私有数据指针,通常用于指向设备驱动自定义以用于描述设备的结构体
inode结构体
- 该结构体是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁
dev_t i_rdev
:包含设备编号,高12位为主设备号,低20位为次设备号unsigned int iminor(struct inode *inode)
:获取次设备号unsigned int imajor(struct inode *inode)
:获取主设备号
/proc/devices
:获知系统中注册的设备
/dev
:查看系统中包含的设备文件
Documents
目录下的devices.txt
文件描述了Linux设备号的分配情况。
devfs(了解)
- 2.6以前可使用,2.6之后被抛弃
- 优点
- 在设备初始化和卸载时,自动的创建和删除设备文件
- 设备驱动程序可以指定设备名、所有者和权限位,用户空间程序依然可以修改所有者和权限为
- 可以自动的获得可用的主设备号,以及指定次设备号
register_chrdev()
:传递0,自动获得可用的主设备号devfs_register()
:指定次设备号
devfs_mk_dir()
:创建设备目录devfs_register()
:创建设备文件devfs_unregister()
:撤销设备文件
udev用户空间设备管理(位于用户空间)
- udev取代了devfs
- udev位于用户空间,而devfs位于内核空间
- 因为Linux设计中,内核只负责机制,不应该实现策略,说人话就是,内核只负责提供思想,而应用层负责把思想进行具体的应用
- 如,内核只负责提供一个加法机制,但不限制谁加谁,限制是应用层需要做的事情。
- udev是利用设备加入或移除时门内核所发送的热插拔时间来工作
- 热插拔时,设备的详细信息会由内核通过
netlink套接字
发送出来,发出的事件叫uevent
。udev
的设备命名策略、权限控制和事件处理都是在用户态下完成的,它利用从内核收到的信息来进行创建设备文件节点等工作
- 热插拔时,设备的详细信息会由内核通过
示例代码,热插拔
#include <linux/netlink.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
static void die(char *s)
{
write(2,s,strlen(s));
exit(1);
}
int main(int argc,char* argv[])
{
struct sockaddr_nl nls;
struct pollfd pfd;
char buf[512];
memset(&nls,0,sizeof(struct sockaddr_nl));
nls.nl_family = AF_NETLINK;
nls.nl_pid = getpid();
nls.nl_groups = -1;
pfd.events = POLLIN;
pfd.fd = socket(PF_NETLINK,SOCK_DGRAM,NETLINK_KOBJECT_UEVENT);
if(pfd.fd == -1){
die("Not root\n");
}
if(bind(pfd.fd,(void *)&nls,sizeof(struct sockaddr_nl))){
die("Bind failed\n");
}
while(-1 != poll(&pfd,1,-1)){
int i,len = recv(pfd.fd,buf,sizeof(buf),MSG_DONTWAIT);
if(len == -1)
die("recv\n");
i=0;
while(i<len){
printf("%s\n",buf+i);
i += strlen(buf+i) + 1;
}
}
die("poll\n");
return 0;
}
冷插拔
- 冷插拔:在udev启动前就已经插入的设备。
- 向
/sys/module/psmouse/uevent
写一个add
,就会导致内核重新发送netlink
uevent
节点位于sysfs
下面
sysfs文件系统与Linux设备模型
sysfs
是一个虚拟的文件系统,可以产生一个包括所有系统硬件的层级视图,与proc
文件系统类似sysfs
:把连接在系统上的设备和总线组织成一个分级的文件,可以被用户空间存取,向用户空间导出内核数据结构以及他们的属性。- 可以通过
sysfs
知晓设备驱动模型中各组件的层次关系 /sys/block
:包含所有的块设备/sys/devices
:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构/sys/bus
:包含系统中所有的总线类型。- 在
./pci
等子目录下,会分出drivers
和devices
目录,而devices
中是对/sys/devices
目录中文件的符号链接
- 在
/sys/class
:包含系统中的设备类型,如网卡设备
- 可以通过
- 在Linux内核中:
bus_type
:描述总线device_driver
:描述设备驱动device
:描述设备- 这三个结构体定义在:
include/linux/device.h
- 由于驱动和设备都必须依附于总线,故都包含
struct bus_type
zhiz
- 由于驱动和设备都必须依附于总线,故都包含
- 在Linux内核中,设备和驱动是分开注册的。
- 通过
bus_type
的match()
成员函数进行匹配
- 通过
- 总线、驱动和设备实际上都可以认为是
kobject
的派生类,一个kobject
对应sysfs
中的一个目录 - 总线、设备和驱动中的各个
attribute
都会成为sysfs
中的一个文件,读写attribute
使用对应结构体中的show()
和store()
函数
遍历sysfs,找出所有的设备,并分析出来设备名和主次设备号
#!/bin/bash
# Populate block devices
for i in /sys/block/*/dev /sys/block/*/*/dev
do
if [ -f $i ]
then
MAJOR=$(sed 's/:.*//' < $i)
MINOR=$(sed 's/.*://' < $i)
DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
echo /dev/$DEVNAME b $MAJOR $MINOR
#mknod /dev/$DEVNAME b $MAJOR $MINOR
fi
done
# Populate char devices
for i in /sys/bus/*/devices/*/dev /sys/class/*/*/dev
do
if [ -f $i ]
then
MAJOR=$(sed 's/:.*//' < $i)
MINOR=$(sed 's/.*://' < $i)
DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
echo /dev/$DEVNAME c $MAJOR $MINOR
#mknod /dev/$DEVNAME c $MAJOR $MINOR #为整个系统中的设备建立/dev/下面的节点
fi
done
sed 's/:.*//'
和sed -e 's@/dev@@'
中,第一个的/
和第二个的@
作用一直,都是用于分割命令的,第一个命令是指:匹配:
后面的序列,并删除;第二个命令是指:匹配/dev
,并删除/dev
udev的组成
udev
目前和systemd
项目合并- 文档:
https://lwn.net/Articles/490413/
- 下载最新代码:
http://cgit.freedesktop.org/systemd/
、https://github.com/systemd/systemd
- 文档:
- 工作过程
- 内核检测到系统中出现了新设备,内核就通过
netlink
套接字发送uevent
udev
获取到内核发送的信息,进行规则的匹配。匹配的事务包括SUBSYSTEM
、ACTION
、attribute
、内核提供的名称以及其他的环境变量
- 内核检测到系统中出现了新设备,内核就通过
- 捕获
uevent
包含的信息:udevadm monitor --kernel --property --udev
在U盘插入时,自动为该U盘创建一个/dev/kingstonUD
的符号链接
#Kingston USB mass storage
SUBSYSTEM=="block",ACTION=="add",KERNEL=="*sd?",ENV{ID_TYPE}=="disk",
ENV{ID_VENDOR}=="Kingston",ENV{ID_USB_DRIVER}=="usb-storage",SYMLINK+="kingstonUD"
SYMLINK+="kingstonUD"
:自动创建一个kingstonUD
符号链接- 文件应当放置在
/etc/udev/rules.d
下
udev规则文件
- 规则文件的关键字
- 匹配关键字
ACTION
:行为KERNEL
:匹配内核设备名BUS
:匹配总线类型SUBSYSTEM
:匹配子系统名ATTR
:属性等
- 赋值关键字
NAME
:创建的设备文件名SYMLINK
:符号创建链接名OWNER
:设置设备的所有者GROUP
:设置设备的组IMPORT
:调用外部程序MODE
:节点访问权限等
- 也可以使用正则表达式,如
*
:通配符?
:代替一个字符%k
:代表KERNEL
%n
:代表设备的KERNEL
序号
- 匹配关键字
- 查看内核信息和
sysfs
属性信息udevadm info -a -p 指定的设备名
,如udevadm info -a -p /sys/devices/platform/serial8250/tty/ttyS1
- 查看
/dev
节点对应于/sys
的具体节点路径udevadm info -a -p $(udevadm info -q path -n /dev/ttyS1)
- 输出的信息中:紧随
looking at device
其后的便是
udev的轻量级版本:mdev;
android中的是vold