linux0.12-10-5-rs_io.s

这篇具有很好参考价值的文章主要介绍了linux0.12-10-5-rs_io.s。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

[534页]

10-5 rs_io.s程序

10-5-1 功能描述

该汇编程序实现rs232串行通信中断处理过程。在进行字符的传输和存储过程中,该中断过程主要对终端的读、写缓冲队列进行操作。它把从串行线路上接收到的字符存入串行终端的读缓冲队列read_q中,或把写缓冲队列write_q中需要发送出去的字符通过串行线路发送给远端串行终端设备。

引起系统发生串行中断的情况有4中:
1、 由于modem状态发生了变化;
2、 由于线路状态发生了变化;
3、 由于接收到字符;
4、 由于在中断允许标志寄存器中设置了发送保持寄存器中断允许标志,需要发送字符。

对于引起中断的前两种情况的处理过程是通过读取对应状态寄存器值,从而使其复位。
对于由于接收到字符的情况,程序首先把该字符放入读缓冲队列read_q中,然后
调用copy_to_cooked()函数转换成以字符行为单位的规范模式字符放入辅助队列secondary中。
对于需要发送字符的情况,则程序首先从写缓冲队列write_q尾指针处中取出一个字符发送出去,再判断写缓冲队列是否已空,若还有字符则循环执行发送操作。

因此,在阅读本程序之前,最好先看一下include/linux/tty.h头文件。其中给出了字符缓冲队列的数据结构tty_queue、终端的数据结构tty_struct和一些控制字符的值。另外还有一些对缓冲队列进行操作的宏定义。缓冲队列及其操作示意图参见图10-14。文章来源地址https://www.toymoban.com/news/detail-459070.html

10-5-2 代码注释

/*
 *  linux/kernel/rs_io.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *	rs_io.s
 *
 * 该模块实现rs232输入输出中断处理程序。
 */

.text
.globl _rs1_interrupt,_rs2_interrupt

// size是读写队列缓冲区的字节长度。该值必须是2的次方,并且必须与tty_io.c中的匹配。
size	= 1024				/* must be power of two !
					   and must match the value
					   in tty_io.c!!! */

/* 以下这些是读写缓冲队列结构中的偏移量 */
// 对应 include/linux/tty.h 文件中 tty_queue 结构中各字段的字节偏移量。其中rs_addr
// 对应tty_queue结构的data字段。对于串行终端缓冲队列,该字段存放着串行端口基地址。
rs_addr = 0				// 串行端口号字段偏移(端口是0x3f8或0x2f8)。
head = 4				// 缓冲区中头指针字段偏移。
tail = 8				// 缓冲区中尾指针字段偏移。
proc_list = 12			// 等待该缓冲的进程字段偏移。
buf = 16				// 缓冲区字段偏移。

// 当一个写缓冲队列满后,内核就会把要往写队列填字符的进程设置为等待状态。当写缓冲队列
// 中还剩余最多256个字符时,中断处理程序就可以唤醒这些等待进程继续往写队列中放字符。
startup	= 256		 /* 当我们重新开始写时,队列里最多还剩余字符个数。*/

/*
 * 这些是实际的中断处理程序。程序首先检查中断的来源,然后执行
 * 相应的处理。
 */
 串行端口1中断处理程序入口点。
// 初始化时rs1_interrupt地址被放入中断描述符0x24中,对应8259A的中断请求IRQ4引脚。
// 这里首先把tty表中串行终端1(串口1)读写缓冲队列指针的地址入栈(tty_io.c,81),
// 然后跳转到rs_int继续处理。这样做可以让串口1和串口2的处理代码公用。字符缓冲队列
// 结构tty_queue格式请参见include/linux/tty.h,第22行。
.align 2
_rs1_interrupt:
	pushl $_table_list+8		// tty表中串口1读写缓冲队列指针地址入栈。
	jmp rs_int

.align 2
 串行端口2中断处理程序入口点。
_rs2_interrupt:
	pushl $_table_list+16		// tty表中串口2读写缓冲队列指针地址入栈。
	
// 这段代码首先让段寄存器ds、es指向内核数据段,然后从对应读写缓冲队列data字段取出
// 串行端口基地址。该地址加2即是中断标识寄存器IIR的端口地址。若位0 = 0,表示有需
// 要处理的中断。于是根据位2、位1使用指针跳转表调用相应中断源类型处理子程序。在每
// 个子程序中会在处理完后复位UART 的相应中断源。在子程序返回后这段代码会循环判断是
// 否还有其他中断源(位0 = 0?)。如果本次中断还有其他中断源,则IIR的位0仍然是0。
// 于是中断处理程序会再调用相应中断源子程序继续处理。直到引起本次中断的所有中断源都
// 被处理并复位,此时UART会自动地设置IIR的位0 =1,表示已无待处理的中断,于是中断
// 处理程序即可退出。	
rs_int:
	pushl %edx
	pushl %ecx
	pushl %ebx
	pushl %eax
	push %es
	push %ds		/* as this is an interrupt, we cannot */
	pushl $0x10		/* know that bs is ok. Load it */
	pop %ds			/* 由于这是一个中断程序,我们不知道ds是否正确,*/
	pushl $0x10		/* 所以加载它们(让ds、es指向内核数据段) */
	pop %es
	movl 24(%esp),%edx		// 取上面[pushl $_table_list+8]或[pushl $_table_list+16]行入栈的相应串口缓冲队列指针地址。
	movl (%edx),%edx		// 取读缓冲队列结构指针(地址)->edx。
	movl rs_addr(%edx),%edx	// 取串口1(或串口2)端口基地址->edx。
	addl $2,%edx			/* interrupt ident. reg *//* 指向中断标识寄存器 */
							// 中断标识寄存器端口地址是0x3fa(0x2fa)。
rep_int:
	xorl %eax,%eax
	inb %dx,%al				// 取中断标识字节,以判断中断来源(有4种中断情况)。
	testb $1,%al			// 首先判断有无待处理中断(位0 = 0有中断)
	jne end					// 若无待处理中断,则跳转至退出处理处end。
	cmpb $6,%al				/* this shouldn't happen, but ... */*这不会发生,但…*/
	ja end					// al值大于6,则跳转至end(没有这种状态)。
	movl 24(%esp),%ecx		// 调用子程序之前把缓冲队列指针地址放入ecx。
	pushl %edx				// 临时保存中断标识寄存器端口地址。
	subl $2,%edx			// edx中恢复串口基地址值0x3f8(0x2f8)。
	call jmp_table(,%eax,2)		/* NOTE! not *4, bit0 is 0 already */
    // 上面语句是指,当有待处理中断时,al中位0=0,位2、位1是中断类型,因此相当于已经将
    // 中断类型乘了2,这里再乘2,获得跳转表(第79行)对应各中断类型地址,并跳转到那里去
    // 作相应处理。中断来源有4种:modem状态发生变化;要写(发送)字符;要读(接收)字符;
    // 线路状态发生变化。允许发送字符中断通过设置发送保持寄存器标志实现。在serial.c程序
    // 中,当写缓冲队列中有数据时,rs_write()函数就会修改中断允许寄存器内容,添加上发送保
    // 持寄存器中断允许标志,从而在系统需要发送字符时引起串行中断发生。	
	
	popl %edx				// 恢复中断标识寄存器端口地址0x3fa(或0x2fa)。
	jmp rep_int				// 跳转,继续判断有无待处理中断并作相应处理。
end:	movb $0x20,%al		// 中断退出处理。向中断控制器发送结束中断指令EOI。
	outb %al,$0x20			/* EOI */
	pop %ds
	pop %es
	popl %eax
	popl %ebx
	popl %ecx
	popl %edx
	addl $4,%esp		# jump over _table_list entry # 丢弃队列指针地址。
	iret

// 各中断类型处理子程序地址跳转表,共有4种中断来源:
// modem状态变化中断,写字符中断,读字符中断,线路状态有问题中断。
jmp_table:
	.long modem_status,write_char,read_char,line_status

// 由于modem状态发生变化而引发此次中断。通过读modem状态寄存器MSR对其进行复位操作。
.align 2
modem_status:
	addl $6,%edx		/* clear intr by reading modem status reg */
	inb %dx,%al			/* 通过读modem状态寄存器进行复位(0x3fe) */
	ret

// 由于线路状态发生变化而引起这次串行中断。通过读线路状态寄存器LSR对其进行复位操作。
.align 2
line_status:
	addl $5,%edx		/* clear intr by reading line status reg. */
	inb %dx,%al			/* 通过读线路状态寄存器进行复位(0x3fd) */
	ret

// 由于UART芯片接收到字符而引起这次中断。对接收缓冲寄存器执行读操作可复位该中断源。
// 这个子程序将接收到的字符放到读缓冲队列read_q头指针(head)处,并且让该指针前移一
// 个字符位置。若head指针已经到达缓冲区末端,则让其折返到缓冲区开始处。最后调用C函
// 数do_tty_interrupt()(也即copy_to_cooked()),把读入的字符经过处理放入规范模式缓
// 冲队列(辅助缓冲队列secondary)中。
.align 2
read_char:
	inb %dx,%al				// 读取接收缓冲寄存器RBR中字符->al。
	movl %ecx,%edx			// 当前串口缓冲队列指针地址->edx。
	subl $_table_list,%edx	// 当前串口队列指针地址 - 缓冲队列指针表首址 ->edx,
	shrl $3,%edx			// 差值/8,得串口号。对于串口1是1,对于串口2是2。
	movl (%ecx),%ecx		# read-queue // 取读缓冲队列结构地址->ecx。
	movl head(%ecx),%ebx	// 取读队列中缓冲头指针->ebx。
	movb %al,buf(%ecx,%ebx)	// 将字符放在缓冲区中头指针所指位置处。
	incl %ebx				// 将头指针前移(右移)一字节。
	andl $size-1,%ebx		// 用缓冲区长度对头指针取模操作。
	cmpl tail(%ecx),%ebx	// 缓冲区头指针与尾指针比较。
	je 1f					// 若指针移动后相等,表示缓冲区满,不保存头指针,跳转。
	movl %ebx,head(%ecx)	// 保存修改过的头指针。
1:	addl $63,%edx			// 串口号转换成tty号(63或64)并作为参数入栈。
	pushl %edx
	call _do_tty_interrupt	// 调用tty中断处理C函数(tty_io.c,342行)。
	addl $4,%esp			// 丢弃入栈参数,并返回。
	ret

// 由于设置了发送保持寄存器允许中断标志而引起此次中断。说明对应串行终端的写字符缓冲队
// 列中有字符需要发送。于是计算出写队列中当前所含字符数,若字符数已小于256个,则唤醒
// 等待写操作进程。然后从写缓冲队列尾部取出一个字符发送,并调整和保存尾指针。如果写缓
// 冲队列已空,则跳转到write_buffer_empty处处理写缓冲队列空的情况。
.align 2
write_char:
	movl 4(%ecx),%ecx		# write-queue	// 取写缓冲队列结构地址->ecx。
	movl head(%ecx),%ebx	// 取写队列头指针->ebx。
	subl tail(%ecx),%ebx	// 头指针 - 尾指针 = 队列中字符数。
	andl $size-1,%ebx		# nr chars in queue
	je write_buffer_empty	// 若头指针 = 尾指针,说明写队列空,跳转处理。
	cmpl $startup,%ebx		// 队列中字符数还超过256个?
	ja 1f					// 超过则跳转处理。
	movl proc_list(%ecx),%ebx	# 唤醒等待的进程。
								// 取等待该队列的进程指针,并判断是否为空。
	testl %ebx,%ebx				# 有等待写的进程吗?
	je 1f						// 是空的,则向前跳转到标号1处。
	movl $0,(%ebx)				// 否则将进程置为可运行状态(唤醒进程)。
1:	movl tail(%ecx),%ebx		// 取尾指针。
	movb buf(%ecx,%ebx),%al		// 从缓冲中尾指针处取一字符->al。
	outb %al,%dx				// 向端口0x3f8(0x2f8)写到发送保持寄存器中。
	incl %ebx					// 尾指针前移。
	andl $size-1,%ebx			// 尾指针若到缓冲区末端,则折回。
	movl %ebx,tail(%ecx)		// 保存已修改过的尾指针。
	cmpl head(%ecx),%ebx		// 尾指针与头指针比较,
	je write_buffer_empty		// 若相等,表示队列已空,则跳转。
	ret
	
// 处理写缓冲队列write_q已空的情况。若有等待写该串行终端的进程则唤醒之,然后屏蔽发
// 送保持寄存器空中断,不让发送保持寄存器空时产生中断。
// 如果此时写缓冲队列write_q已空,表示当前无字符需要发送。于是我们应该做两件事情。
// 首先看看有没有进程正等待写队列空出来,如果有就唤醒之。另外,因为现在系统已无字符
// 需要发送,所以此时我们要暂时禁止发送保持寄存器THR空时产生中断。当再有字符被放入
// 写缓冲队列中时,serial.c中的rs_write()函数会再次允许发送保持寄存器空时产生中断,
// 因此UART就又会“自动”地来取写缓冲队列中的字符,并发送出去。	
.align 2
write_buffer_empty:
	movl proc_list(%ecx),%ebx	# 唤醒等待的进程。
	testl %ebx,%ebx				# 有等待的进程吗?
	je 1f						// 无,则向前跳转到标号1处。
	movl $0,(%ebx)				// 否则将进程置为可运行状态(唤醒进程)。
1:	incl %edx					// 指向端口0x3f9(0x2f9)。
	inb %dx,%al					// 读取中断允许寄存器IER。
	jmp 1f						// 稍作延迟。
1:	jmp 1f						/* 屏蔽发送保持寄存器空中断(位1) */
1:	andb $0xd,%al				/* disable transmit interrupt */
	outb %al,%dx				// 写入0x3f9(0x2f9)。
	ret

到了这里,关于linux0.12-10-5-rs_io.s的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux0.12-8-6-sched.c

    [315页] sched.c是内核中有关任务(进程)调度管理的程序,其中包括有关调度的基本函数 (sleep_on(0、wakeup()、schedule()等)以及一些简单的系统调用函数(比如getpid())。 系统时钟中断处理过程中调用的定时函数do_timer()也被放置在本程序中。 另外,为了便于软盘驱动器定时处理的编

    2024年02月03日
    浏览(28)
  • Linux0.12内核源码解读(2)-Bootsect.S

    大家好,我是呼噜噜,在上一篇文章聊聊x86计算机启动发生的事?我们了解了x86计算机启动过程,MBR、0x7c00是什么?其中当bios引导结束后,操作系统接过计算机的控制权后,发生了哪些事?本文将揭开迷雾的序章- Bootsect.S 我们先来回顾一下,上古时期计算机按下电源键的启

    2024年04月12日
    浏览(30)
  • 【12章 Java IO流】程序员必看 IO流 详解

    ❤爱在开头 ❤博客主页: 努力的小鳴人 ❤系列专栏: JavaSE超详总结😋 ❤欢迎小伙伴们, 点赞👍关注🔎收藏🍔 一起学习! ❤如有错误的地方,还请小伙伴们指正!🌹 🔥系列传送门 : JavaSE超详总结😋 【附章5计算机字符编码】多种字符编码集的说明【热榜】 【11章J

    2023年04月16日
    浏览(26)
  • Linux0.11内核源码解析-exec.c

    主要实现对二进制可执行文件和shell文件的加载和执行,其中主要的函数是do_execve(),它是系统中断调用int 0x80的功能号__NR_execve()调用,是exec()函数的主要实现以下几点功能: 1.执行对参数和环境参数空间页面的初始化操作,初始化空间页面指针数组,根据执行文件名取执行对

    2024年02月06日
    浏览(27)
  • Linux0.11内核源码解析-truncate.c

    truncate文件只要实现释放指定i节点在设备上占用的所有逻辑块,包括直接块、一次间接块、二次间接块。从而将文件节点对应的文件长度截为0,并释放占用的设备空间。  

    2024年02月12日
    浏览(33)
  • 【项目分析】仿linux0.11的操作系统内核

    系列综述: 💞目的:本系列是个人整理为了 秋招面试 的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于 《操作系统 真象还原》及各大佬博客 进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验

    2024年02月09日
    浏览(31)
  • 【探索Linux】—— 强大的命令行工具 P.12(文件描述符 | 重定向 | 基础IO)

    前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的一些知识,也学习了一些Linux的基本操作,也了解并学习了有关Linux开发工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代码也相信大家都掌握的不错,上一篇文章我们了解了基础IO,文件操作,今天

    2024年02月08日
    浏览(40)
  • 【探索Linux】文件描述符 | 重定向 | 基础IO —— 强大的命令行工具 P.12

    前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的一些知识,也学习了一些Linux的基本操作,也了解并学习了有关Linux开发工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代码也相信大家都掌握的不错,上一篇文章我们了解了基础IO,文件操作,今天

    2024年02月08日
    浏览(40)
  • 【Linux0.11代码分析】04 之 head.s 启动流程

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《【Linux0.11代码分析】03 之 setup.s 启动流程》 4.《【Linux0.11代码分析】04 之 head.s 启动流程

    2024年02月03日
    浏览(30)
  • Linux0.11内核源码解析-read_write.c

    目录  sys_lseek read write read_write.c主要是实现文件系统调用read(),write()和lseek()三个功能 read和write函数分别是调用file_dev.c/pipe.c/block_dev.c/char_dev.c实现相对应的函数 lseek实现系统调用将对文件句柄对应文件结果体中的当前读写指针进行修改,对于读写指针不能移动的文件和管道文

    2024年02月13日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包