linux设备辅助设备号,Linux设备驱动开发学习(4):字符设备驱动(未完)

本章的目的是编写一个完整字符设备驱动。例子代码来自于LDD3的scull范例。scull字符设备是对

内存的操作,不涉及具体的硬件,所以理论上在任何一台机器上都可以测试,这很方便。

4.1 scull的设计

scull字符设备的驱动将默认对应4个scull设备(文件),scull0-scull3。当然也可以通过在编译时、加载时、运行时3种不同的方式改变scull设备(文件)的数目,后面将逐一说明每种方式的具体做法。

4.2 字符设备的主、次设备号

字符设备的存取是通过其在文件系统中的名字来存取,就如普通的文件通过名字来存取一样。通过

ls -l /dev命令查看/dev目录,可看到类似如下输出:

4988e015e24e562fabdfcac7cc1a3d7c.png

其中第1列的字母为c的设备为字符类设备,而第5列的10, 175和10, 235分别表示的字符设备的主、次设

备号:其中的10表示主设备号,175和235表示次设备号。

21ef837c8ec431b35eb29061a816f307.png

通常来讲,主设备号用来连接设备驱动,如上图中/dev/null和/dev/zero都是由同一驱动来管理。

但也不总是这样,现代的Linux允许多个设备驱动共享同一主设备号,但大部分还是一个主设备号对应一

个驱动。

4.2.1 设备号的内部表示

设备号在内部用定义在中的dev_t类型来表示。设备号的实际类型随着内核版本的变化而变化,不能简单的将设备号认为是某个特定类型,且主次设备号的位分布也是如此。

头文件提供了如下的辅助宏来帮助我们管理设备号:

MAJOR(dev_t dev):获取设备的主设备号;

MINOR(dev_t dev):获取设备的从设备号;

MKDEV(int major, int minor):根据指定的主次设备号构造dev_t。

4.2.2 分配、释放设备号

设备号由系统统一管理,所以驱动使用的设备号需向系统申请。字符驱动申请设备号有静态和动态

两种方式。

静态申请设备号函数:int register_chrdev_region(dev_t first, unsigned int count, char *name);

参数first为驱动想要申请的第一个设备号,count参数指出从设备号first开始想要申请的连续的设备号

个数,name参数代表申请的设备号区间。name参数指定的名称会出现在/proc/devices文件中。函数调用

成功返回0,否则返回负数。

动态申请设备号函数:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,

unsigned int count, char *name);

参数dev指向申请返回的设备号,参数firstminor指定第一个从设备号,剩余参数的含义同静态申请函

数。函数调用成功返回0,否则返回负数。

申请的设备号最终都必须要被释放。释放字符设备号的函数为:void unregister_chrdev_region(dev_t first, unsigned int count);

释放设备号的时机通常是在设备驱动模块的退出函数中。

4.2.3 动态分配主设备号

部分主设备号已被静态分配给一些通用设备,可以查看源码文档目录下Documentation/devices.txt

查看预分配的主设备号。采用动态分配方式通常是更加的设备号分配方案,但动态分配方式也有它的缺

点:我们事先无法得知分配到的设备号,但这并不成问题,因为一旦设备号被分配后,你可以查看文件

/proc/devices来获得主设备号。如图:

1376d7f7d158e09911db7bf02102dfcb.png

图中第1列是设备的主设备号。

4.2.4 scull模块的加载和卸载、scull设备文件的创建和删除

我们在测试scull驱动的时候需要先创建scull字符设备,我们可以通过下面的脚本来完成scull设备的创建和删除。

scull模块加载、scull设备文件创建脚本:#!/bin/sh

# scull_load

module="scull"

device="scull"

mode="664" # rw-rw-r--

group="bill" # change it to your user name

# invoke insmod with all arguments we got

/sbin/insmod ./$module.ko $* || exit 1

# remove stale nodes

rm -f /dev/${device}[0-3]

majors=($(awk "\$2==\"$module\" {print \$1}" /proc/devices))

major=${majors[0]}

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

ln -sf ${device}0 /dev/${device}

# give appropriate group/permissions, and change the group

chgrp $group /dev/${device}[0-3]

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

scull模块卸载、scull设备文件删除脚本:#!/bin/sh

# scull_unload

module="scull"

device="scull"

# invoke rmmod with all arguments we got

/sbin/rmmod $module $* || exit 1

# Remove stale nodes

rm -f /dev/${device} /dev/${device}[0-3]

另外,除了手动加载scull模块外,Linux系统还提供模块的自动加载机制。

方法一:

(1) 拷贝模块到内核模块目录sudo cp scull.ko /lib/modules/`uname -r`/kernel/drivers/char/scull.ko

(2) 添加模块名到启动时加载模块列表文件/etc/modules:sudo vi /etc/modules

在打开的文件中添加一行:scull

如果是带参数启动模块,在上面行的后面带模块参数即可,类似于insmod加载模块带参数的情况。

(3) 更新内核模块依赖文件sudo depmod

(4) 重启系统,用lsmod命令可查看模块是否成功加载。

方法二:

(1) 拷贝模块到内核模块目录sudo cp scull.ko /lib/modules/`uname -r`/kernel/drivers/char/scull.ko

(2) 编写模块加载脚本/etc/init.d/scull,该脚本会在系统启动时被调用#!/bin/sh -e

modprobe scull

同样,scull模块的参数也可以在添加在modprobe调用行的后面。

创建模块加载脚本的软链接:sudo ln -s /etc/init.d/scull /etc/rcS.d/Sscull

(3) 更新内核模块依赖文件sudo depmod

(4) 重启系统,用lsmod命令可查看模块是否成功加载。

4.3 几个重要数据结构的说明

在正式编写字符设备驱动前,先介绍几个字符设备驱动中会用到重要数据结构。struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

long (*fallocate)(struct file *file, int mode, loff_t offset,

loff_t len);

};

结构体file_operations定义在头文件中,简单来讲,除了第1个成员owner外,其他的成员无

一例外是字符设备所支持的操作,如果这些表示操作的函数指针被置为NULL的话,会在被调用时执行内核缺

省的操作。下面对file_operations中的一些成员作简单说明。

struct

module *owner

表示拥有该结构的模块,用于防止该结构还在使用的情况下模块被卸载。对该成员的赋值,几乎无一例外被

赋成THIS_MODULE。

loff_t (*llseek) (struct file *, loff_t, int);

用于改变文件的读写位置。

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

文件的读写操作。

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

发起文件的异步读写操作。

int (*readdir) (struct file *, void *, filldir_t);

设备文件应该将其置为NULL。该成员主要是用于文件系统。

unsigned int (*poll) (struct file *, struct poll_table_struct *);

系统调用poll,epoll,select最终都对应到该成员,用于查询对一个或多个文件描述符的读写操作是否会被

阻塞。poll可能是进程进入睡眠。如果该成员被赋为NULL,会假定对该设备的读写操作不会被阻塞。

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

设备特定的命令。

int (*mmap) (struct file *, struct vm_area_struct *);

将设备的内存空间映射到进程的地址空间。

int (*open) (struct inode *, struct file *);

设备执行的第一个操作,可以为NULL。

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

当file结构体被释放的时候调用。

int (*fsync) (struct file *, int datasync);

fsync系统调用最终对应到该成员,刷新任何挂起的数据。

int (*aio_fsync) (struct kiocb *, int datasync);

fsync的异步版本。

int (*lock) (struct file *, int, struct file_lock *);

用于实现文件锁。

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

按page发送数据。

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

没有被映射的设备存储空间。

int (*check_flags)(int);

用于检查传送给fcntl(F_SETFL...)的flags。

struct file {

/*

* fu_list becomes invalid after file_free is called and queued via

* fu_rcuhead for RCU freeing

*/

union {

struct list_headfu_list;

struct rcu_head fu_rcuhead;

} f_u;

struct pathf_path;

#define f_dentryf_path.dentry

#define f_vfsmntf_path.mnt

const struct file_operations*f_op;

spinlock_tf_lock;  /* f_ep_links, f_flags, no IRQ */

#ifdef CONFIG_SMP

intf_sb_list_cpu;

#endif

atomic_long_tf_count;

unsigned int f_flags;

fmode_tf_mode;

loff_tf_pos;

struct fown_structf_owner;

const struct cred*f_cred;

struct file_ra_statef_ra;

u64f_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_headf_ep_links;

#endif /* #ifdef CONFIG_EPOLL */

struct address_space*f_mapping;

#ifdef CONFIG_DEBUG_WRITECOUNT

unsigned long f_mnt_write_state;

#endif

};

结构体file定义在头文件中。该结构体用于描述1个打开的文件描述符,和c库的FILE结构体毫

无关系。由内核在open调用时创建,在最后一次close,内核释放该结构体。下面是对结构体的部分成员的

描述。

fmode_tf_mode;

标记文件是可读还是可写等,如FMODE_READ,FMODE_WRITE。

unsigned int f_flags;

文件标记,如O_RDONLY,O_NONBLOCK,O_SYNC等。

const struct file_operations *f_op;

文件绑定的操作。

void *private_data;

文件自定义数据。

struct inode {

/* RCU path lookup touches following: */

umode_ti_mode;

uid_ti_uid;

gid_ti_gid;

const struct inode_operations*i_op;

struct super_block*i_sb;

spinlock_ti_lock;/* i_blocks, i_bytes, maybe i_size */

unsigned inti_flags;

struct mutexi_mutex;

unsigned longi_state;

unsigned longdirtied_when;/* jiffies of first dirtying */

struct hlist_nodei_hash;

struct list_headi_wb_list;/* backing dev IO list */

struct list_headi_lru;/* inode LRU list */

struct list_headi_sb_list;

union {

struct list_headi_dentry;

struct rcu_headi_rcu;

};

unsigned longi_ino;

atomic_ti_count;

unsigned inti_nlink;

dev_ti_rdev;

unsigned inti_blkbits;

u64i_version;

loff_ti_size;

#ifdef __NEED_I_SIZE_ORDERED

seqcount_ti_size_seqcount;

#endif

struct timespeci_atime;

struct timespeci_mtime;

struct timespeci_ctime;

blkcnt_ti_blocks;

unsigned short          i_bytes;

struct rw_semaphorei_alloc_sem;

const struct file_operations*i_fop;/* former ->i_op->default_file_ops */

struct file_lock*i_flock;

struct address_space*i_mapping;

struct address_spacei_data;

#ifdef CONFIG_QUOTA

struct dquot*i_dquot[MAXQUOTAS];

#endif

struct list_headi_devices;

union {

struct pipe_inode_info*i_pipe;

struct block_device*i_bdev;

struct cdev*i_cdev;

};

__u32i_generation;

#ifdef CONFIG_FSNOTIFY

__u32i_fsnotify_mask; /* all events this inode cares about */

struct hlist_headi_fsnotify_marks;

#endif

#ifdef CONFIG_IMA

atomic_ti_readcount; /* struct files open RO */

#endif

atomic_ti_writecount;

#ifdef CONFIG_SECURITY

void*i_security;

#endif

#ifdef CONFIG_FS_POSIX_ACL

struct posix_acl*i_acl;

struct posix_acl*i_default_acl;

#endif

void*i_private; /* fs or device private pointer */

};

结构体inode定义在头文件中。内核用该结构体描述文件,因此该结构体和用于描述打开文件

描述符的file结构体不同。下面是对该结构体部分成员的描述。

dev_t i_rdev;

描述设备文件,包含设备文件的设备号。

union {

struct pipe_inode_info*i_pipe;

struct block_device*i_bdev;

struct cdev*i_cdev;

};

管道设备、块设备、字符设备在内核内部的表示。

另外,有2个用于从inode结构体获取设备号的辅助函数。unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

4.4 字符设备注册

4.4.1 字符设备的分配和初始化

字符设备用下面的结构体来描述:struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

可以通过静态或动态的方式来分配字符设备结构。动态分配的方式如下:struct cdev *my_cdev = cdev_alloc();

my_cdev->ops = &my_fops;

静态分配的字符设备结构可使用cdev_init()来初始化,该接口原型为:void cdev_init(struct cdev *, const struct file_operations *);

4.4.2 挂接字符设备到内核

一旦在定义并初始化好字符设备后,可调用cdev_add()添加字符设备到内核,该接口原型为:int cdev_add(struct cdev *p, dev_t dev, unsigned count);

对该接口需要特别注意的是,一旦调用完成,字符设备就被挂接到内核,立马就有可能投入使用,所以必须

确保在该调用结束前,字符设备的一切准备工作已经就绪。

4.4.3 从内核删除字符设备void cdev_del(struct cdev *p);

4.5 字符设备scull的实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值