第020讲 I/O体系结构与访问设备

本文详细介绍了Linux内核中的I/O体系结构,包括与外设通信的三种问题、系统总线和代表性的总线类型。此外,讨论了设备的访问方式,如I/O端口、I/O内存映射以及轮询和中断策略。通过设备特殊文件(设备文件)来访问扩展设备,区分字符设备和块设备。设备驱动程序在设备与系统之间起到关键的通信作用,而设备号用于区分不同的设备。最后,提到了网卡的特殊处理方式,即通过套接字进行通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 I/O体系结构

  1. 与外设的通信通常称之为输入输出,一般都缩写为I/O。在实现外设I/O时,内核必须处理3个能出现的问题:

    1. 必须根据具体的设备类型和模型,使用各种方法对硬件寻址。
    2. 内核必须向用户应用程序和系统工具提供访问各种设备的方法。但凡有可能,都应当采用统一的方案,确保程序设计的工作量不会过多,同时保证应用程序能够在不考虑特定硬件的情况下进行交互操作。
    3. 用户空间需要知道内核中有哪些设备可用。
  2. 硬件设备可能以多种方式连接到系统,主板的扩展槽或外部连接器最常见的方法,当然,扩展硬件也可以直接集成到主板上。

    1. 系统总线
      尽管外设的范围可能看上去是无限的,但它们并不直接连接CPU,而是通过总线连接起来。总线负责设备与CPU之间各个设备的通信。如下是代表性的总线:
      a. PCI(Peripheral Component Interconnect)
      b. ISA(Industrial Standard Architecture)
      c. SBus
      d. IEEE1394
      e. SCSI(Small Computer System Interface)
      f. USB(Universal Serial Bus)
      g. 并口与串口(Parallel and Serial Interface)

    2. 与外设交互
      与外设通信的方法,有几种方法可用连接到系统的硬件通信

      1、I/O端口
      使用IA-32和很多其他体系结构上都用I/O端口,在这种情况下,内核发送数据给I/O控制器。数据的目标设备通过唯一的端口号标识,数据被传输到设备进行处理。处理器管理一个独立的虚拟地址空间,可以用于管理所以I/O地址。I/O地址空间不关联到普通系统内核。因为端口也可以映射到内存中,这样会引起混淆。在IA-32体系结构上,端口地址空间由2的16次方个不同的8位地址组成,通过0到0xFFFF之间的数字唯一标识。
      2、I/O内存映射
      程序员必须寻址很多设备,与内存的处理方式类似。因此现在处理器提供对I/O进行内存映射,将特殊外设的端口地址映射到普通内存中。
      3、轮询和中断
      轮询(polling)策略比较简单,只需要重复询问设备数据是否可用,如果可用,则处理器取回数据。
      中断策略比较好,每个CPU都提供中断线,可有各个系统设备共享,每个中断通过一个唯一号码标识,内核对使用的每个中断提供一个服务例程。

    3. 通过总线控制设备
      并非所有设备都是直接通过I/O语句寻址,也有通过总线系统访问的。具体的方式与所 使用的总线和设备相关。并非所有设备类别都可以连接到所有总线系统(比如:将硬盘和CD记 录机连接到SCSI接口,但图形卡就行)。

二、访问设备

设备特殊文件(设备文件)用于访问扩展设备。这些文件并不关联到硬盘或任何其他 的存储介质上的数据段,而是建立了与某个设备驱动程序的连接,以支持与扩展设备的通信。 就应用程序而言,普通文件和设备文件的处理有一定差别。二者都是可以通过同样的 库函数处理。

  1. 字符设备、块设备和其他设备
    根据外设与系统之间交换数据的方法,可以将设备分为几种类别。有些设备非常适合 于面向字符的数据交换,因为数据传输量很低。其他的设备则更适合于处理包含固定数目字节 的数据块。内核会区分字符设备和块设备。
    字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机读取,相反,此类设备支持按字节/字符读取。
    块设备:应用程序访问设备数据,程序可自行确定读取数据的位置。比如:硬盘是块设备,应用程序可以寻址磁盘上的任何位置,并读取数据。

  2. /dev目录下一些成员,特别在访问权限上面,访问之前的字母是b或c,分别代表块设备和字符设备;设备文件没有文件长度,而增加另外两个值,分别是主设备号和从设备号。

  3. /dev/hda, /dev/hdb, /dev/sda, /dev/sdb (hda一般指IDE接口的硬盘,sda一般指sata接口硬盘)

  4. 热插拔消息:新设备
    每当内核检测到一个新设备时,都会创建一个内核对象kobject。该对象借助于sysfs文件系统导出到用户层,内核还向用户空间发送一个热插拔消息。

  5. 网卡和其他设备

    字符设备和块设备不是内核管理的全部设备类别。网卡在内核中具有特殊地位,它无法融入到分类方案当中,事实很明显:网卡没有设备文件。相反,用户程序必须使用套接字与网卡通信。套接字就是一个抽象层,对所有网卡提供一个抽象视图。标准库的网络相关函数调用sockercall系统调用与内核通信交互进而访问网卡。

设备驱动程序
用于与系统连接的输入/输出装置通信,如硬盘、软驱、各种接口、声卡等。设备驱动程序的任务在于支持应用程序经由设备文件设备通信,使得能够按照适当的方式在设备上读取/写入数据。
设备号:就是系统分配的一个编号,设备号是一个无符号的32位整型,包括主设备号+次设备号,主设备号为高12位,次设备号为低20位。
设备文件需要设备号才能够创建,设备驱动也需要设备号才能装载,设备文件正是通过主设备号找到它的驱动;设备驱动正式利用次设备号才知道要操作的是哪一个设备。

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))	//获取主设备号ma
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))	//获取次设备号mi
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))		//通过主次设备号获取设备号dev
struct cdev {
	struct kobject kobj;	//内嵌内核对象
	struct module *owner;	//该字符设备所在内核模块的对象指针
	const struct file_operations *ops;	//描述字符设备所能够操作集(打开、关闭、读/写...)
	struct list_head list;	// 用来将已向内核注册的所以设备设备形成链表
	dev_t dev;	//字符设备的设备号,由主设备号和次设备号构成
	unsigned int count;	//隶属于同一主设备号的次设备号的个数
};
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 (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	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 *, loff_t, loff_t, 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 **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};
// 所有字符设备都通过此类型进行记录
struct kobj_map {
	struct probe {
		struct probe *next;	//形成链表结构
		dev_t dev;	//设备号
		unsigned long range;	//设备号的范围
		struct module *owner;
		kobj_probe_t *get;
		int (*lock)(dev_t, void *);
		void *data;		//指向struct cdev 对象
	} *probes[255];
	struct mutex *lock;
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值