第6章 设备驱动程序(1)

目录

6.1 I/O体系结构

6.2 访问设备

6.2.1 设备文件

6.2.2 字符设备、块设备和其他设备

6.2.3 ioctl 寻址设备

6.2.4 主从设备号的表示

6.2.5 注册


本专栏文章将有70篇左右,欢迎+关注,查看后续文章。

6.1 I/O体系结构

1. 扩展硬件

总线的作用:

        用于连接CPU和外设。

总线分为:

        系统总线:

                PCI-E,ISA,EISA

        内部总线:

                SPI,I2C

        外部总线:

                USB,RS-232,RS-485,SCSI

SCSI:

        特点:吞吐非常高。

        使用场景:服务器中寻址硬盘。

2. 与外设的交互

1. IO端口:

        1. 端口号用于识别设备。

        2. 每个端口未使用或分配给一个设备。

        3. 多个设备不能共享同一外设。

        4. 可将两个连续8位端口合并成一个16位端口。

        使用的体系:X86

                端口分为:只读端口,只写端口,读写端口。

                相应函数:outb,outw,inb

2. IO内存映射:

将IO端口进行内存映射,像操作内存一样读写外设。

使用场景:

        显卡,PCI。

相应函数:

        ioremap,iounmap。

        作用:

                将外设的物理地址映射到内核的虚拟地址空间。

轮询polling:

        反复向设备查询是否有可用数据。

        缺点:浪费CPU。

中断:

        多个设备可共享同一中断。

3. 通过总线控制设备

        如PCI,USB等

6.2 访问设备

6.2.1 设备文件

设备文件的作用:

        建立了与设备驱动程序的连接。

        如/dev/ttyS0。

创建设备文件时,创建对应struct inode。

打开设备文件时,创建对应struct file。

6.2.2 字符设备、块设备和其他设备

1. 标识设备文件

#ls -l /dev/ttyS0

        crw-rw---- 1 root dialout 4, 64 4月 4 15:18 /dev/ttyS0

#ls -l /dev/sda1

        brw-rw---- 1 root disk 8, 1 4月 4 15:18 /dev/sda1

c:表示字符设备。

b:表示块设备。

创建设备文件时需指定主从设备号。

        主设备号:用于寻找设备驱动。

        从设备号:用于区分该驱动中的不同设备。

2. 动态创建设备文件

设备文件创建方法有:

        1. 制作文件系统时生成。(静态)

        2. mknod命令创建。

        3. udevd守护进程。 (用户层动态创建)

udevd实现原理:

        1. 内核通过netlink将设备热插拔消息(包含主从设备号信息)发送到用户空间udevd。

        2. udevd监听netlink uevent事件,并创建设备文件。

struct         kset {         //表示一组具有相似特性kobject集合

        struct list_head         list;

        struct kobject            kobj;

        struct kset_uevent_ops     *uevent_ops;

};

static const struct kset_uevent_ops device_uevent_ops = {

        .uevent         =         dev_uevent,

};

6.2.3 ioctl 寻址设备

ioctl:

        配置和修改特定设备的特殊属性。

sysfs文件系统:

        层次化表示系统设备。

        设置设备的参数。

ioctl系统调用:

        sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)

网卡及其他设备

应用层使用socket与网卡通信。

网络相关函数都使用socketcall系统调用与内核通信,从而访问网卡。

哪些设备没有对应设备文件?

        网卡。

        USB设备。

6.2.4 主从设备号的表示

数据结构:dev_t

        共32位,12位主设备号,20从设备号。

API与宏:

        MAJOR,MINOR宏:

                从dev_t提取主,从设备号。

        MKDEV(major, minor):

                根据主从,设备号生成dev_t。

6.2.5 注册

1. 数据结构

字符设备与块设备:都用唯一设备号标识。

struct cdev:

        表示一个字符设备。

struct genhd:

        表示一个块设备分区。

如何跟踪内核所有cdev和genhd实例?

全局数组:

        struct kobj_map         *bdev_map;

        struct kobj_map         *cdev_map;

        一个用于管理系统所有设备的散列表。

        散列方法:主设备号 % 255

struct         kobj_map {         //设备数据库

        struct probe {

                struct probe    *next;

                dev_t                 dev;         //设备号

                unsigned long   range;         //从设备号范围

                struct module    *owner;

                kobj_probe_t    *get;         //函数指针,用于返回设备的kobject实例

                int (*lock)(dev_t,   void *);

                void                 *data;

                        //字符设备指向struct cdev。

                        //块设备指向struct genhd。

        } *probes[255];

        struct mutex         *lock;

};

字符设备范围数据库

字符设备专用,用于管理设备号的范围。

同样使用主设备为散列键。

struct         char_device_struct {

        struct char_device_struct         *next;

        unsigned int         major;

        int                         minorct;         //从设备数量

        unsigned int         baseminor;         //包含minorct个从设备号的连续范围中最小值

        char                     name[64];

        struct cdev          *cdev;

} *chrdevs[255];

2. 注册过程

注册字符设备

步骤1. 分配一个设备号范围。

两种方法:

方法1.

int register_chrdev_region(dev_t from, unsigned count, const char *name)

        //指定设备号范围。

        使用举例:

                register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0);

方法2.

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char *name)

        //由内核选择合适的设备号范围。

        参数:

                baseminor:指定最小从设备号。

                count:设备号范围的长度。

步骤2. 将设备添加到字符设备数据库。

1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)

        //初始化一个struct cdev。

2. int cdev_add(struct cdev *p, dev_t dev, unsigned count)

        //加入到设备数据库(即struct kobj_map *cdev_map)

        参数:

                count:从设备数量。

cdev_add成功返回后,设备进入活动状态。

注册块设备

void   add_disk(struct gendisk    *disk);

老版本内核也可用:

int    register_chrdev(unsigned int   major, const char   *name,  const struct file_operations   *fops)

int   register_blkdev(unsigned int   major, const char   *name);

  • 33
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山下小童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值