BruceFan's Blog

Stay hungry, stay foolish

0%

Linux设备驱动(三)文件系统

Linux文件系统与设备驱动

图中所示为Linux中虚拟文件系统(VFS)、磁盘/Flash文件系统及一般的设备文件与设备驱动程序之间的关系。

应用程序和VFS之间的接口是系统调用,而VFS与文件系统以及设备文件之间的接口是file_operations结构体成员函数,这个结构体包含对文件进行打开、关闭、读写、控制的一系列成员函数。

file结构体

file结构体代表一个打开的文件,系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。在内核和驱动源代码中,struct file的指针通常被命名为filefilp
代码清单 include/linux/fs.h: file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;

/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;

u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;

#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};

inode结构体

VFS inode包含文件访问权限、所有者、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
代码清单 include/linux/fs.h: inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct inode {
umode_t i_mode; // inode的权限
unsigned short i_opflags;
kuid_t i_uid; // inode所有者的id
kgid_t i_gid; // inode所属的群组id
unsigned int i_flags;
...
dev_t i_rdev; // 若是设备文件,此字段将记录设备的设备号
loff_t i_size; // inode所代表的文件大小
struct timespec i_atime; // inode最近一次的存取时间
struct timespec i_mtime; // inode最近一次的修改时间
struct timespec i_ctime; // inode的产生时间
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
unsigned int i_blkbits;
blkcnt_t i_blocks; // inode所使用的block数,一个block为512字节
...
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev; // 若是块设备,为其对应的block_device结构体指针
struct cdev *i_cdev; // 若是字符设备,为其对应的cdev结构体指针
};
...

查看/proc/devices文件可以获知系统中注册的设备,第一列为主设备号,第二列为设备名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
...
Block devices:
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
...

查看/dev目录可以获知系统中包含的设备文件,日期前的两列对应设备的主设备号和次设备号:

1
2
3
4
$ ls -al /dev
crw-r----- 1 root kmem 1, 4 3月 5 14:03 port
crw-rw---- 1 root dip 108, 0 3月 5 14:03 ppp
crw-rw-rw- 1 root tty 5, 2 3月 5 14:32 ptmx

主设备号是与驱动对应的概念,同一类设备一般用相同的主设备号,不同类设备的主设备号一般不同。

udev用户空间设备管理

udev完全在用户态工作,利用设备加入或移除时内核所发送的热插拔事件(Hotplug Event)来工作。在热插拔时,设备的详细信息会由内核通过netlink套接字发送出来,发出的事件叫ueventudev的设备命名策略、权限控制和事件处理都是在用户态下完成的,它利用从内核收到的信息来进行创建设备文件节点等工作。下面给出从内核通过netlink接收热插拔事件的使用范例:
代码清单 netlink.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <linux/types.h>
#include <linux/netlink.h>

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];

// Open hotplug event netlink socket

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

// Listen to netlink socket

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

// Print the data to stdout.
i = 0;
while (i<len) {
printf("%s\n", buf+i);
i += strlen(buf+i)+1;
}
}
die("poll\n");

// Dear gcc: shut up.
return 0;
}

编译上述程序并运行,出入一个金士顿的U盘,该程序会打印出类似如下的信息。

1
2
3
4
5
6
7
8
9
10
11
ACTION=add
DEVLINKS=/dev/disk/by-id/usb-Kingston_DataTraveler_2.0_00241D8CE3F71F70290F0510-0:0-part4 /dev/disk/by-label/Ubuntu\x2016.0 /dev/disk/by-path/pci-0000:00:06.0-usb-0:2:1.0-scsi-0:0:0:0-part4 /dev/disk/by-uuid/B4FE-5315
DEVNAME=/dev/sdb4
DEVPATH=/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0/host3/target3:0:0/3:0:0:0/block/sdb/sdb4
DEVTYPE=partition
ID_BUS=usb
ID_FS_LABEL=Ubuntu_16.0
ID_FS_LABEL_ENC=Ubuntu\x2016.0
ID_FS_TYPE=vfat
ID_FS_USAGE=filesystem
ID_

udev就是采用这种方式接收netlink消息,并根据它的内容和用户设置给udev的规则做匹配来进行工作的。
reference
《Linux设备驱动开发详解——基于最新的Linux 4.0内核》