linux0.12-10-6-tty_io.c

news2024/12/26 20:55:35

[539页]

10-6 tty_io.c程序

10-6-1 功能描述

每个tty设备有3个缓冲队列,分别是读缓冲队列(read_q)、写缓冲队列(write_q)和辅助缓冲队列(secondary),定义在tty_struct结构中(include/linux/tty.h)。
对于每个缓冲队列,读操作是从缓冲队列的左端取字符,并且把缓冲队列尾巴(tail)指针向右移动。而写操作则是往缓冲队列的右端添加字符,并且也把头(head)指针向右移动。这两个指针中,任何一个若移动到超出了缓冲队列的末端,则折回到左端重新开始。
如图10-14所示。
在这里插入图片描述

本程序包括字符设备的上层接口函数。
主要含有终端读/写函数tty_read()和tty_write()。
读操作的行规则函数copy_to_cooked()也在这里实现。

tty_read()和tty_write()将在文件系统中用于操作字符设备文件时被调用。
例如当一个程序读/dev/tty文件时,就会执行系统调用sys_read(0(在fs/read_write.c中),而这个系统调用在判别处所读文件是一个字符设备文件时,即会调用rw_char()函数(在fs/char_dev.c中),该函数则会根据所读设备的子设备号等信息,由字符设备读写函数表(设备开关表)调用rw_tty(),最终调用到这里的终端读操作函数tty_read()。

copy_to_cooked()函数由键盘中断过程调用(通过do_tty_interrupt()),用于根据终端termios结构中设置值的字符输入/输出标志(例如INLCR、OUCLC)对read_q队列中的字符进行处理,把字符转换以字符行为单位的规范模式字符序列,并保存在辅助字符缓冲队列(规范模式缓冲队列)(secondary)中,供上述tty_read()读取。在转换处理期间,若终端的回显标志L_ECHO置位,则还会把字符放入写队列write_q中,并调用终端写函数把该字符显示在屏幕上。如果是串行终端,那么写函数将时rs_write()(在serial.c,53行)。rs_write()会把串行终端写队列中的字符通过串行线路发送给串行终端,并显示在串行终端的屏幕上。copy_to_cooked()函数最后还将唤醒等待着辅助缓冲队列的进程。函数实现的步骤如下所示:
1、 如果读队列空或者辅助队列已经满,则跳转到最后一步(第10步),否则执行以下操作。
2、 从读队列read_q的尾指针处取一字符,并且尾指针前移一字符位置。
3、 若是回车(CR)或换行(NL)字符,则根据终端termios结构中输入标志(ICRNL、INLCR、INOCR)的状态,
对该字符作相应转换。例如,如果读取的是一个回车字符并且ICRNL标志是置位的,则把它替换成换行字符。
4、 若大写小写标志IUCLC是置位的,则把字符替换成对应的小写字符。
5、 若规范模式标志ICANON是置位的,则对该字符进行规范模式处理。
a.若是删行字符(^U),则删除secondary中的一行字符(队列头指针后退,直到遇到回车或换行或队列已空为止);
b.若是擦除字符(^H),则删除secondary中头指针处的一个字符,头指针后退一个字符位置;
c.若是停止字符(^S),则设置终端的停止标志stopped=1;
d.若是开始字符(^Q),则复位终端的停止标志。
6、 如果接收键盘信号标志ISIG是置位的,则为进程生成对应键入控制字符的信号。
7、 如果是行结束字符(例如NL或^D),则辅助队列secondary的行数统计值data增1.
8、 如果本地回显标志是置位的,则把字符也放入写队列write_q中,并调用终端写函数在屏幕上显示该字符。
9、 把该字符放入辅助队列secondary中,返回第1步继续循环处理读队列中其他字符。
10、 最后唤醒睡眠在辅助队列上的进程。

在阅读下面的程序前不妨首先查看一下include/linux/tty.h头文件。在该头文件定义了tty字符缓冲队列的数据结构以及一些宏操作定义。另外还定义了控制字符的ASCII码值。

10-6-2 代码注释

/*
 *  linux/kernel/tty_io.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
* 'tty_io.c'给tty终端一种非相关的感觉,不管它们是控制台还是串行终端。
* 该程序同样实现了回显、规范(熟)模式等。
*
* Kill-line问题,要谢谢John T Kohl。他同时还纠正了当 VMIN = VTIME = 0 时的问题。
*/

#include <ctype.h>		// 字符类型头文件。定义了一些有关字符类型判断和转换的宏。
#include <errno.h>		// 错误号头文件。包含系统中各种出错号。
#include <signal.h>		// 信号头文件。定义信号符号常量,信号结构及其操作函数原型。
#include <unistd.h>		// unistd.h是标准符号常数与类型文件,并声明了各种函数。

// 给出定时警告(alarm)信号在信号位图中对应的比特屏蔽位。
#define ALRMMASK (1<<(SIGALRM-1))

#include <linux/sched.h>	// 调度程序头文件,定义了任务结构task_struct、任务0数据等。
#include <linux/tty.h>		// tty头文件,定义了有关tty_io,串行通信方面的参数、常数。
#include <asm/segment.h>	// 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
#include <asm/system.h>		// 系统头文件。定义设置或修改描述符/中断门等嵌入式汇编宏。

// 终止进程组(向进程组发送信号)。参数pgrp指定进程组号;sig指定信号;priv权限。
// 即向指定进程组pgrp中的每个进程发送指定信号sig。只要向一个进程发送成功最后就会
// 返回0,否则如果没有找到指定进程组号pgrp的任何一个进程,则返回出错号-ESRCH,若
// 找到进程组号是pgrp的进程,但是发送信号失败,则返回发送失败的错误码。
int kill_pg(int pgrp, int sig, int priv);	// kernel/exit.c,171行。
// 判断一个进程组是否是孤儿进程。如果不是则返回0;如果是则返回1。
int is_orphaned_pgrp(int pgrp);		// kernel/exit.c,232行。

// 获取termios结构中三个模式标志集之一,或者用于判断一个标志集是否有置位标志。
#define _L_FLAG(tty,f)	((tty)->termios.c_lflag & f)
#define _I_FLAG(tty,f)	((tty)->termios.c_iflag & f)
#define _O_FLAG(tty,f)	((tty)->termios.c_oflag & f)

// 取termios结构终端特殊(本地)模式标志集中的一个标志。
#define L_CANON(tty)	_L_FLAG((tty),ICANON)		// 取规范模式标志。
#define L_ISIG(tty)	_L_FLAG((tty),ISIG)				// 取信号标志。
#define L_ECHO(tty)	_L_FLAG((tty),ECHO)				// 取回显字符标志。
#define L_ECHOE(tty)	_L_FLAG((tty),ECHOE)		// 规范模式时取回显擦出标志。
#define L_ECHOK(tty)	_L_FLAG((tty),ECHOK)		// 规范模式时取KILL擦除当前行标志。
#define L_ECHOCTL(tty)	_L_FLAG((tty),ECHOCTL)		// 取回显控制字符标志。
#define L_ECHOKE(tty)	_L_FLAG((tty),ECHOKE)		// 规范模式时取KILL擦除行并回显标志。
#define L_TOSTOP(tty)	_L_FLAG((tty),TOSTOP)		// 对于后台输出发送SIGTTOU信号。

// 取termios结构输入模式标志集中的一个标志。
#define I_UCLC(tty)	_I_FLAG((tty),IUCLC)	// 取大写到小写转换标志。
#define I_NLCR(tty)	_I_FLAG((tty),INLCR)	// 取换行符NL转回车符CR标志。
#define I_CRNL(tty)	_I_FLAG((tty),ICRNL)	// 取回车符CR转换行符NL标志。
#define I_NOCR(tty)	_I_FLAG((tty),IGNCR)	// 取忽略回车符CR标志。
#define I_IXON(tty)	_I_FLAG((tty),IXON)		// 取输入控制流标志XON。

// 取termios结构输出模式标志集中的一个标志。
#define O_POST(tty)	_O_FLAG((tty),OPOST)		// 取执行输出处理标志。
#define O_NLCR(tty)	_O_FLAG((tty),ONLCR)		// 取换行符NL转回车换行符CR-NL标志。
#define O_CRNL(tty)	_O_FLAG((tty),OCRNL)		// 取回车符CR转换行符NL标志。
#define O_NLRET(tty)	_O_FLAG((tty),ONLRET)	// 取换行符NL执行回车功能的标志。
#define O_LCUC(tty)	_O_FLAG((tty),OLCUC)		// 取小写转大写字符标志。

// 取termios结构控制标志集中波特率。CBAUD是波特率屏蔽码(0000017)。
#define C_SPEED(tty)	((tty)->termios.c_cflag & CBAUD)
// 判断tty终端是否已挂线(hang up),即其传输波特率是否为B0(0)。
#define C_HUP(tty)	(C_SPEED((tty)) == B0)

// 取最小值宏。
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

// 下面定义tty终端使用的缓冲队列结构数组tty_queues 和 tty终端表结构数组tty_table。
// QUEUES是tty终端使用的缓冲队列最大数量。伪终端分主从两种(master和slave)。每个
// tty终端使用3个tty 缓冲队列,它们分别是用于缓冲键盘或串行输入的读队列 read_queue、
// 用于缓冲屏幕或串行输出的写队列 write_queue,以及用于保存规范模式字符的辅助缓冲队列
// secondary。
#define QUEUES	(3*(MAX_CONSOLES+NR_SERIALS+2*NR_PTYS))		// 共54项。
static struct tty_queue tty_queues[QUEUES];					// tty缓冲队列数组。
struct tty_struct tty_table[256];							// tty表结构数组。


// 下面设定各种类型的tty终端所使用缓冲队列结构在tty_queues[]数组中的起始项位置。
// 8个虚拟控制台终端占用tty_queues[]数组开头24项(3 X MAX_CONSOLES)(0 -- 23);
// 两个串行终端占用随后的6项(3 X NR_SERIALS)(24 -- 29)。
// 4个主伪终端占用随后的12项(3 X NR_PTYS)(30 -- 41)。
// 4个从伪终端占用随后的12项(3 X NR_PTYS)(42 -- 53)。
#define con_queues tty_queues
#define rs_queues ((3*MAX_CONSOLES) + tty_queues)
#define mpty_queues ((3*(MAX_CONSOLES+NR_SERIALS)) + tty_queues)
#define spty_queues ((3*(MAX_CONSOLES+NR_SERIALS+NR_PTYS)) + tty_queues)

// 下面设定各种类型tty终端所使用的tty结构在tty_table[]数组中的起始项位置。
// 8个虚拟控制台终端可用tty_table[]数组开头64项(0 -- 63);
// 两个串行终端使用随后的2项(64 -- 65)。
// 4个主伪终端使用从128开始的项,最多64项(128 -- 191)。
// 4个从伪终端使用从192开始的项,最多64项(192 -- 255)。
#define con_table tty_table			// 定义控制台终端tty表符号常数。
#define rs_table (64+tty_table)		// 串行终端tty表。
#define mpty_table (128+tty_table)	// 主伪终端tty表。
#define spty_table (192+tty_table)	// 从伪终端tty表。

int fg_console = 0;

/*
 * 下面是汇编程序中使用的缓冲队列结构地址表。通过修改这个表,
 * 你可以实现虚拟控制台。
 */
// tty读写缓冲队列结构地址表。供rs_io.s程序使用,用于取得读写缓冲队列结构的地址。
struct tty_queue * table_list[]={
	con_queues + 0, con_queues + 1,		// 前台控制台读、写队列结构地址。
	rs_queues + 0, rs_queues + 1,		// 串行终端1读、写队列结构地址。
	rs_queues + 3, rs_queues + 4		// 串行终端2读、写队列结构地址。
	};

 改变前台控制台。
// 将前台控制台设定为指定的虚拟控制台。
// 参数:new_console - 指定的新控制台号。
void change_console(unsigned int new_console)
{
    // 如果参数指定的控制台已经在前台或者参数无效,则退出。否则设置当前前台控制台号,同
    // 时更新table_list[]中的前台控制台读、写队列结构地址。最后更新当前前台控制台屏幕。
	if (new_console == fg_console || new_console >= NR_CONSOLES)
		return;
	fg_console = new_console;
	table_list[0] = con_queues + 0 + fg_console*3;
	table_list[1] = con_queues + 1 + fg_console*3;
	update_screen();		// kernel/chr_drv/console.c,936行。
}

 如果队列缓冲区空则让进程进入可中断睡眠状态。
// 参数:queue - 指定队列的指针。
// 进程在取队列缓冲区中字符之前需要调用此函数加以验证。如果当前进程没有信号要处理,
// 并且指定的队列缓冲区空,则让进程进入可中断睡眠状态,并让队列的进程等待指针指向
// 该进程。
static void sleep_if_empty(struct tty_queue * queue)
{
	cli();
	while (!(current->signal & ~current->blocked) && EMPTY(queue))
		interruptible_sleep_on(&queue->proc_list);
	sti();
}

 若队列缓冲区满则让进程进入可中断的睡眠状态。
// 参数:queue - 指定队列的指针。
// 进程在往队列缓冲区中写入字符之前需要调用此函数判断队列情况。
static void sleep_if_full(struct tty_queue * queue)
{
    // 如果队列缓冲区不满则返回退出。否则若进程没有信号需要处理,并且队列缓冲区中空闲剩
    // 余区长度 < 128,则让进程进入可中断睡眠状态,并让该队列的进程等待指针指向该进程。
	if (!FULL(queue))
		return;
	cli();
	while (!(current->signal & ~current->blocked) && LEFT(queue)<128)
		interruptible_sleep_on(&queue->proc_list);
	sti();
}
 等待按键。
// 如果前台控制台读队列缓冲区空,则让进程进入可中断睡眠状态。
void wait_for_keypress(void)
{
	sleep_if_empty(tty_table[fg_console].secondary);
}
 复制成规范模式字符序列。
// 根据终端termios结构中设置的各种标志,将指定tty终端读队列缓冲区中的字符复制转换
// 成规范模式(熟模式)字符并存放在辅助队列(规范模式队列)中。
// 参数:tty - 指定终端的tty结构指针。
void copy_to_cooked(struct tty_struct * tty)
{
	signed char c;
    // 首先检查当前终端tty结构中缓冲队列指针是否有效。如果三个队列指针都是NULL,则说明
    // 内核tty初始化函数有问题。
	if (!(tty->read_q || tty->write_q || tty->secondary)) {
		printk("copy_to_cooked: missing queues\n\r");
		return;
	}
	// 否则我们根据终端termios结构中的输入和本地标志,对从tty读队列缓冲区中取出的每个
    // 字符进行适当的处理,然后放入辅助队列secondary中。在下面循环体中,如果此时读队列
    // 缓冲区已经取空或者辅助队列缓冲区已经放满字符,就退出循环体。否则程序就从读队列缓
    // 冲区尾指针处取一字符,并把尾指针前移一个字符位置。然后根据该字符代码值进行处理。
    // 另外,如果定义了_POSIX_VDISABLE(\0),那么在对字符处理过程中,若字符代码值等于
    // _POSIX_VDISABLE的值时,表示禁止使用相应特殊控制字符的功能。
	while (1) {
		if (EMPTY(tty->read_q))
			break;
		if (FULL(tty->secondary))
			break;
		GETCH(tty->read_q,c);// 取一字符到c,并前移尾指针。
		
		// 如果该字符是回车符CR(13),那么若回车转换行标志CRNL置位,则将字符转换为换行符
		// NL(10)。否则如果忽略回车标志NOCR置位,则忽略该字符,继续处理其他字符。如果字
		// 符是换行符NL(10),并且换行转回车标志NLCR置位,则将其转换为回车符CR(13)。
		if (c==13) {
			if (I_CRNL(tty))
				c=10;
			else if (I_NOCR(tty))
				continue;
		} else if (c==10 && I_NLCR(tty))
			c=13;
			
		// 如果大写转小写输入标志UCLC置位,则将该字符转换为小写字符。	
		if (I_UCLC(tty))
			c=tolower(c);
			
		// 如果本地模式标志集中规范模式标志CANON 已置位,则对读取的字符进行以下处理。 首先,
		// 如果该字符是键盘终止控制字符KILL(^U),则对已输入的当前行执行删除处理。删除一行字
		// 符的循环过程如是:如果 tty辅助队列不空,并且取出的辅助队列中最后一个字符不是换行
		// 符NL(10),并且该字符不是文件结束字符(^D),则循环执行下列代码:
		// 如果本地回显标志ECHO 置位,那么:若字符是控制字符(值 < 32),则往tty写队列中放
		// 入擦除控制字符ERASE(^H)。然后再放入一个擦除字符ERASE,并且调用该tty写函数,把
		// 写队列中的所有字符输出到终端屏幕上。 另外,因为控制字符在放入写队列时需要用2个字
		// 节表示(例如^V),因此要求特别对控制字符多放入一个ERASE。最后将tty辅助队列头指针
		// 后退1字节。另外,如果定义了_POSIX_VDISABLE(\0),那么在对字符处理过程中,若字符
		// 代码值等于 _POSIX_VDISABLE的值时,表示禁止使用相应特殊控制字符的功能。	
		if (L_CANON(tty)) {
			if ((KILL_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==KILL_CHAR(tty))) {
				/* deal with killing the input line */
				while(!(EMPTY(tty->secondary) ||
				        (c=LAST(tty->secondary))==10 ||
				        ((EOF_CHAR(tty) != _POSIX_VDISABLE) &&
					 (c==EOF_CHAR(tty))))) {
					if (L_ECHO(tty)) {// 若本地回显标志置位。
						if (c<32)// 控制字符要删2字节。
							PUTCH(127,tty->write_q);
						PUTCH(127,tty->write_q);
						tty->write(tty);
					}
					DEC(tty->secondary->head);
				}
				continue;// 继续读取读队列中字符进行处理。
			}
			
			// 如果该字符是删除控制字符ERASE(^H),那么:如果tty的辅助队列为空,或者其最后一个
			// 字符是换行符NL(10),或者是文件结束符,则继续处理其他字符。如果本地回显标志ECHO置
			// 位,那么:若字符是控制字符(值< 32),则往tty的写队列中放入擦除字符ERASE。再放入
			// 一个擦除字符ERASE,并且调用该tty的写函数。最后将tty辅助队列头指针后退1字节,继
			// 续处理其他字符。同样地,如果定义了_POSIX_VDISABLE(\0),那么在对字符处理过程忠,
			// 若字符代码值等于 _POSIX_VDISABLE 的值时,表示禁止使用相应特殊控制字符的功能。
			if ((ERASE_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==ERASE_CHAR(tty))) {
				if (EMPTY(tty->secondary) ||
				   (c=LAST(tty->secondary))==10 ||
				   ((EOF_CHAR(tty) != _POSIX_VDISABLE) &&
				    (c==EOF_CHAR(tty))))
					continue;
				if (L_ECHO(tty)) {// 若本地回显标志置位。
					if (c<32)
						PUTCH(127,tty->write_q);
					PUTCH(127,tty->write_q);
					tty->write(tty);
				}
				DEC(tty->secondary->head);
				continue;
			}
		}
		
		// 如果设置了IXON标志,则使终端停止/开始输出控制字符起作用。如果没有设置此标志,那
		// 么停止和开始字符将被作为一般字符供进程读取。在这段代码中,如果读取的字符是停止字
		// 符STOP(^S),则置tty 停止标志,让tty 暂停输出。同时丢弃该特殊控制字符(不放入
		// 辅助队列中),并继续处理其他字符。如果字符是开始字符START(^Q),则复位tty停止
		// 标志,恢复tty输出。同时丢弃该控制字符,并继续处理其他字符。
		// 对于控制台来说,这里的tty->write()是console.c中的con_write()函数。因此控制台将
		// 由于发现stopped=1而会立刻暂停在屏幕上显示新字符(chr_drv/console.c,第586行)。
		// 对于伪终端也是由于设置了终端stopped标志而会暂停写操作(chr_drv/pty.c,第24行)。
		// 对于串行终端,也应该在发送终端过程中根据终端stopped标志暂停发送,但本版未实现。
		if (I_IXON(tty)) {
			if ((STOP_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==STOP_CHAR(tty))) {
				tty->stopped=1;
				tty->write(tty);
				continue;
			}
			if ((START_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==START_CHAR(tty))) {
				tty->stopped=0;
				tty->write(tty);
				continue;
			}
		}
		
		// 若输入模式标志集中ISIG标志置位,表示终端键盘可以产生信号,则在收到控制字符INTR、
		// QUIT、SUSP 或 DSUSP 时,需要为进程产生相应的信号。 如果该字符是键盘中断符(^C),
		// 则向当前进程之进程组中所有进程发送键盘中断信号SIGINT,并继续处理下一字符。 如果该
		// 字符是退出符(^\),则向当前进程之进程组中所有进程发送键盘退出信号SIGQUIT,并继续
		// 处理下一字符。如果字符是暂停符(^Z),则向当前进程发送暂停信号SIGTSTP。同样,若定
		// 义了_POSIX_VDISABLE(\0),那么在对字符处理过程忠,若字符代码值等于_POSIX_VDISABLE
		// 的值时,表示禁止使用相应特殊控制字符的功能。以下不再啰嗦了 :-)
		if (L_ISIG(tty)) {
			if ((INTR_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==INTR_CHAR(tty))) {
				kill_pg(tty->pgrp, SIGINT, 1);
				continue;
			}
			if ((QUIT_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==QUIT_CHAR(tty))) {
				kill_pg(tty->pgrp, SIGQUIT, 1);
				continue;
			}
			if ((SUSPEND_CHAR(tty) != _POSIX_VDISABLE) &&
			    (c==SUSPEND_CHAR(tty))) {
				if (!is_orphaned_pgrp(tty->pgrp))
					kill_pg(tty->pgrp, SIGTSTP, 1);
				continue;
			}
		}
		
		// 如果该字符是换行符NL(10),或者是文件结束符EOF(4,^D),表示一行字符已处理完,
		// 则把辅助缓冲队列中当前含有字符行数值secondary.data增1。如果在函数tty_read()中取
		// 走一行字符,该值即会被减1,参见315行。
		if (c==10 || (EOF_CHAR(tty) != _POSIX_VDISABLE &&
			      c==EOF_CHAR(tty)))
			tty->secondary->data++;
			
		// 如果本地模式标志集中回显标志ECHO在置位状态,那么,如果字符是换行符NL(10),则将
		// 换行符NL(10)和回车符CR(13)放入tty写队列缓冲区中;如果字符是控制字符(值<32)
		// 并且回显控制字符标志ECHOCTL置位,则将字符'^'和字符 c+64 放入tty写队列中(也即会
		// 显示^C、^H等);否则将该字符直接放入tty写缓冲队列中。最后调用该tty写操作函数。
		if (L_ECHO(tty)) {
			if (c==10) {
				PUTCH(10,tty->write_q);
				PUTCH(13,tty->write_q);
			} else if (c<32) {
				if (L_ECHOCTL(tty)) {
					PUTCH('^',tty->write_q);
					PUTCH(c+64,tty->write_q);
				}
			} else
				PUTCH(c,tty->write_q);
			tty->write(tty);
		}
		// 每一次循环末将处理过的字符放入辅助队列中。
		PUTCH(c,tty->secondary);
	}
	// 最后在退出循环体后唤醒等待该辅助缓冲队列的进程(如果有的话)。
	wake_up(&tty->secondary->proc_list);
}

/* 当需要发送信号SIGTTIN 或 SIGTTOU 到我们进程组中所有进程时就会调用该函数。
 *
 * 在进程使用默认信号处理句柄情况下,我们仅要求一个系统调用被重新启动,如果
 * 有系统调用因本信号而被中断。这样做的原因是,如果一个作业正在捕获SIGTTIN
 * 或 SIGTTOU信号,那么相应信号句柄并不会希望系统调用被盲目地重新启动。如果
 * 没有其他方法把终端的pgrp复位到当前pgrp(例如可能由于在logout时控制终端
 * 已被释放),那么我们并不希望在重新启动系统调用时掉入一个无限循环中,并且
 * 总是产生 SIGTTIN 或 SIGTTOU 信号。默认的信号句柄会使得进程停止,因而可以
 * 避免无限循环问题。这里假设可识别作业控制的父进程会在继续执行其子进程之前
 * 把问题搞定。
 */
 向使用终端的进程组中所有进程发送信号。
// 在后台进程组中的一个进程访问控制终端时,该函数用于向后台进程组中的所有进程发送
// SIGTTIN或SIGTTOU信号。无论后台进程组中的进程是否已经阻塞或忽略掉了这两个信号,
// 当前进程都将立刻退出读写操作而返回。
int tty_signal(int sig, struct tty_struct *tty)
{
    // 我们不希望停止一个孤儿进程组中的进程(参见文件kernel/exit.c中第232行上的说明)。
    // 因此如果当前进程组是孤儿进程组,就出错返回。否则就向当前进程组所有进程发送指定信
    // 号sig。
	if (is_orphaned_pgrp(current->pgrp))
		return -EIO;		/* don't stop an orphaned pgrp */
	(void) kill_pg(current->pgrp,sig,1);	/ 发送信号sig。
	// 如果这个信号被当前进程阻塞(屏蔽),或者被当前进程忽略掉,则出错返回。否则,如果
    // 当前进程对信号sig设置了新的处理句柄,那么就返回我们可被中断的信息。否则就返回在
    // 系统调用重新启动后可以继续执行的信息
	if ((current->blocked & (1<<(sig-1))) ||
	    ((int) current->sigaction[sig-1].sa_handler == 1)) 
		return -EIO;		/* Our signal will be ignored */
	else if (current->sigaction[sig-1].sa_handler)
		return -EINTR;		/* We _will_ be interrupted :-) */
	else
		return -ERESTARTSYS;	/* We _will_ be interrupted :-) */
					/* (but restart after we continue) */
}

 tty读函数。
// 从终端辅助缓冲队列中读取指定数量的字符,放到用户指定的缓冲区中。
// 参数:channel - 子设备号;buf – 用户缓冲区指针;nr - 欲读字节数。
// 返回已读字节数。
int tty_read(unsigned channel, char * buf, int nr)
{
	struct tty_struct * tty;
	struct tty_struct * other_tty = NULL;
	char c, * b=buf;
	int minimum,time;
    // 首先判断参数有效性并取终端的tty结构指针。如果tty终端的三个缓冲队列指针都是NULL,
    // 则返回EIO出错信息。如果tty终端是一个伪终端,则再取得另一个对应伪终端的tty结构
    // other_tty。
	if (channel > 255)
		return -EIO;
	tty = TTY_TABLE(channel);
	if (!(tty->write_q || tty->read_q || tty->secondary))
		return -EIO;
		
    // 如果当前进程使用的是这里正在处理的tty终端,但该终端的进程组号却与当前进程组号不
    // 同,表示当前进程是后台进程组中的一个进程,即进程不在前台。于是我们要停止当前进程
    // 组的所有进程。因此这里就需要向当前进程组发送SIGTTIN信号,并返回等待成为前台进程
    // 组后再执行读操作。		
	if ((current->tty == channel) && (tty->pgrp != current->pgrp)) 
		return(tty_signal(SIGTTIN, tty));
		
	// 如果当前终端是伪终端,那么对应的另一个伪终端就是other_tty。若这里tty是主伪终端,
    // 那么other_tty就是对应的从伪终端,反之也然。	
	if (channel & 0x80)
		other_tty = tty_table + (channel ^ 0x40);
		
	// 然后根据 VTIME 和VMIN 对应的控制字符数组值设置读字符操作超时定时值time和最少需
    // 要读取的字符个数minimum。在非规范模式下,这两个是超时定时值。VMIN表示为了满足读
    // 操作而需要读取的最少字符个数。VTIME是一个1/10秒计数计时值。	
	time = 10L*tty->termios.c_cc[VTIME];	// 设置读操作超时定时值。
	minimum = tty->termios.c_cc[VMIN];		// 最少需要读取的字符个数。
	
	// 如果tty终端处于规范模式,则设置最小要读取字符数minimum 等于进程欲读字符数nr。同
    // 时把进程读取nr字符的超时时间值设置为极大值(不会超时)。否则说明终端处于非规范模
    // 式下,若此时设置了最少读取字符数minimum,则先临时设置进城读超时定时值为无限大,以
    // 让进程先读取辅助队列中已有字符。如果读到的字符数不足 minimum 的话,后面代码会根据
    // 指定的超时值time 来设置进程的读超时值 timeout,并会等待读取其余字符。参见328行。
    // 若此时没有设置最少读取字符数minimum(为0),则将其设置为进程欲读字符数nr,并且如
    // 果设置了超时定时值time的话,就把进程读字符超时定时值timeout设置为系统当前时间值
    //  + 指定的超时值time,同时复位time。 另外,如果以上设置的最少读取字符数minimum大
    // 于进程欲读取的字符数nr,则让minimum=nr。即对于规范模式下的读取操作,它不受VTIME
    // 和VMIN对应控制字符值的约束和控制,它们仅在非规范模式(生模式)操作中起作用。
	if (L_CANON(tty)) {
		minimum = nr;
		current->timeout = 0xffffffff;
		time = 0;
	} else if (minimum)
		current->timeout = 0xffffffff;
	else {
		minimum = nr;
		if (time)
			current->timeout = time + jiffies;
		time = 0;
	}
	if (minimum>nr)
		minimum = nr;// 最多读取要求的字符数。
		
	// 现在我们开始从辅助队列中循环取出字符并放到用户缓冲区buf 中。当欲读的字节数大于0,
    // 则执行以下循环操作。在循环过程中,如果当前终端是伪终端,那么我们就执行其对应的另
    // 一个伪终端的写操作函数,让另一个伪终端把字符写入当前伪终端辅助队列缓冲区中。即让
    // 另一终端把写队列缓冲区中字符复制到当前伪终端读队列缓冲区中,并经行规程函数转换后
    // 放入当前伪终端辅助队列中。	
	while (nr>0) {
		if (other_tty)
			other_tty->write(other_tty);
			
		// 如果tty辅助缓冲队列为空,或者设置了规范模式标志并且tty读队列缓冲区未满,并且辅
		// 助队列中字符行数为0,那么,如果没有设置过进程读字符超时值(为0),或者当前进程
		// 目前收到信号,就先退出循环体。否则如果本终端是一个从伪终端,并且其对应的主伪终端
		// 已经挂断,那么我们也退出循环体。如果不是以上这两种情况,我们就让当前进程进入可中
		// 断睡眠状态,返回后继续处理。由于规范模式时内核以行为单位为用户提供数据,因此在该
		// 模式下辅助队列中必须起码有一行字符可供取用,即secondary.data起码是1才行。	
		cli();
		if (EMPTY(tty->secondary) || (L_CANON(tty) &&
		    !FULL(tty->read_q) && !tty->secondary->data)) {
			if (!current->timeout ||
			  (current->signal & ~current->blocked)) {
			  	sti();
				break;
			}
			if (IS_A_PTY_SLAVE(channel) && C_HUP(other_tty))
				break;
			interruptible_sleep_on(&tty->secondary->proc_list);
			sti();
			continue;
		}
		sti();
		
		// 下面开始正式执行取字符操作。需读字符数nr依次递减,直到nr=0或者辅助缓冲队列为空。
		// 在这个循环过程中,首先取辅助缓冲队列字符c,并且把缓冲队列尾指针tail向右移动一个
		// 字符位置。如果所取字符是文件结束符(^D)或者是换行符NL(10),则把辅助缓冲队列中
		// 含有字符行数值减1。 如果该字符是文件结束符(^D)并且规范模式标志成置位状态,则中
		// 断本循环,否则说明现在还没有遇到文件结束符或者正处于原始(非规范)模式。在这种模
		// 式中用户以字符流作为读取对象,也不识别其中的控制字符(如文件结束符)。于是将字符
		// 直接放入用户数据缓冲区 buf中,并把欲读字符数减1。此时如果欲读字符数已为0则中断
		// 循环。另外,如果终端处于规范模式并且读取的字符是换行符NL(10),则也退出循环。
		// 除此之外,只要还没有取完欲读字符数nr并且辅助队列不空,就继续取队列中的字符。
		do {
			GETCH(tty->secondary,c);
			if ((EOF_CHAR(tty) != _POSIX_VDISABLE &&
			     c==EOF_CHAR(tty)) || c==10)
				tty->secondary->data--;
			if ((EOF_CHAR(tty) != _POSIX_VDISABLE &&
			     c==EOF_CHAR(tty)) && L_CANON(tty))
				break;
			else {
				put_fs_byte(c,b++);
				if (!--nr)
					break;
			}
			if (c==10 && L_CANON(tty))
				break;
		} while (nr>0 && !EMPTY(tty->secondary));
		
		// 执行到此,那么如果 tty 终端处于规范模式下,说明我们可能读到了换行符或者遇到了文件
		// 结束符。如果是处于非规范模式下,那么说明我们已经读取了nr个字符,或者辅助队列已经
		// 被取空了。于是我们首先唤醒等待读队列的进程,然后看看是否设置过超时定时值time。如
		// 果超时定时值time不为0,我们就要求等待一定的时间让其他进程可以把字符写入读队列中。
		// 于是设置进程读超时定时值为系统当前时间jiffies + 读超时值time。当然,如果终端处于
		// 规范模式,或者已经读取了nr个字符,我们就可以直接退出这个大循环了。
		wake_up(&tty->read_q->proc_list);
		if (time)
			current->timeout = time+jiffies;
		if (L_CANON(tty) || b-buf >= minimum)
			break;
	}
	// 此时读取tty字符循环操作结束,因此复位进程的读取超时定时值timeout。如果此时当前进
    // 程已收到信号并且还没有读取到任何字符,则以重新启动系统调用号返回。否则就返回已读取
    // 的字符数(b-buf)。
	current->timeout = 0;
	if ((current->signal & ~current->blocked) && !(b-buf))
		return -ERESTARTSYS;
	return (b-buf);
}
 tty写函数。
// 把用户缓冲区中的字符放入tty写队列缓冲区中。
// 参数:channel - 子设备号;buf - 缓冲区指针;nr - 写字节数。
// 返回已写字节数。
int tty_write(unsigned channel, char * buf, int nr)
{
	static cr_flag=0;
	struct tty_struct * tty;
	char c, *b=buf;
    // 首先判断参数有效性并取终端的tty结构指针。如果tty终端的三个缓冲队列指针都是NULL,
    // 则返回EIO出错信息。
	if (channel > 255)
		return -EIO;
	tty = TTY_TABLE(channel);
	if (!(tty->write_q || tty->read_q || tty->secondary))
		return -EIO;
		
	// 如果若终端本地模式标志集中设置了TOSTOP,表示后台进程输出时需要发送信号SIGTTOU。
    // 如果当前进程使用的是这里正在处理的tty终端,但该终端的进程组号却与当前进程组号不
    // 同,即表示当前进程是后台进程组中的一个进程,即进程不在前台。于是我们要停止当前进
    // 程组的所有进程。因此这里就需要向当前进程组发送SIGTTOU信号,并返回等待成为前台进
    // 程组后再执行写操作。	
	if (L_TOSTOP(tty) && 
	    (current->tty == channel) && (tty->pgrp != current->pgrp)) 
		return(tty_signal(SIGTTOU, tty));
		
	// 现在我们开始从用户缓冲区buf中循环取出字符并放到写队列缓冲区中。当欲写字节数大于0,
    // 则执行以下循环操作。在循环过程中,如果此时tty写队列已满,则当前进程进入可中断的睡
    // 眠状态。如果当前进程有信号要处理,则退出循环体。	
	while (nr>0) {
		sleep_if_full(tty->write_q);
		if (current->signal & ~current->blocked)
			break;
			
		// 当要写的字符数nr还大于0并且tty写队列缓冲区不满,则循环执行以下操作。首先从用户
		// 缓冲区中取1字节。如果终端输出模式标志集中的执行输出处理标志 OPOST 置位,则执行对
		// 字符的后处理操作。	
		while (nr>0 && !FULL(tty->write_q)) {
			c=get_fs_byte(b);
			if (O_POST(tty)) {
			// 如果该字符是回车符'\r'(CR,13)并且回车符转换行符标志OCRNL置位,则将该字符换成
			// 换行符'\n'(NL,10);否则如果该字符是换行符'\n'(NL,10)并且换行转回车功能标志
			// ONLRET置位的话,则将该字符换成回车符'\r'(CR,13)。
				if (c=='\r' && O_CRNL(tty))
					c='\n';
				else if (c=='\n' && O_NLRET(tty))
					c='\r';
			// 如果该字符是换行符'\n' 并且回车标志cr_flag没有置位,但换行转回车-换行标志ONLCR
			// 置位的话,则将cr_flag标志置位,并将一回车符放入写队列中。然后继续处理下一个字符。
			// 如果小写转大写标志OLCUC置位的话,就将该字符转成大写字符。		
				if (c=='\n' && !cr_flag && O_NLCR(tty)) {
					cr_flag = 1;
					PUTCH(13,tty->write_q);
					continue;
				}
				if (O_LCUC(tty))// 小写转成大写字符。
					c=toupper(c);
			}
			// 接着把用户数据缓冲指针b前移1字节;欲写字节数减1字节;复位cr_flag标志,并将该
			// 字节放入tty写队列中。
			b++; nr--;
			cr_flag = 0;
			PUTCH(c,tty->write_q);
		}
		// 若要求的字符全部写完,或者写队列已满,则程序退出循环。此时会调用对应tty写函数,
		// 把写队列缓冲区中的字符显示在控制台屏幕上,或者通过串行端口发送出去。如果当前处
		// 理的tty是控制台终端,那么tty->write()调用的是con_write();如果tty是串行终端,
		// 则tty->write()调用的是rs_write()函数。若还有字节要写,则等待写队列中字符取走。
		// 所以这里调用调度程序,先去执行其他任务。
		tty->write(tty);
		if (nr>0)
			schedule();
	}
	return (b-buf);
}

/*
 * 呵,有时我真得很喜欢386。该子程序被从一个中断处理程序中
 * 调用,并且即使在中断处理程序中睡眠也应该绝对没有问题(我
 * 希望如此)。当然,如果有人证明我是错的,那么我将憎恨intel
 * 一辈子J。但是我们必须小心,在调用该子程序之前需要恢复中断。
 *
 * 我不认为在通常环境下会处在这里睡眠,这样很好,因为任务睡眠
 * 是完全任意的。
 */
 tty中断处理调用函数 - 字符规范模式处理。
// 参数:tty - 指定的tty终端号。
// 将指定tty终端队列缓冲区中的字符复制或转换成规范(熟)模式字符并存放在辅助队列中。
// 该函数会在串口读字符中断(rs_io.s,109)和键盘中断(kerboard.S,69)中被调用。
void do_tty_interrupt(int tty)
{
	copy_to_cooked(TTY_TABLE(tty));
}
 字符设备初始化函数。空,为以后扩展做准备。
void chr_dev_init(void)
{
}
 tty终端初始化函数。
// 初始化所有终端缓冲队列,初始化串口终端和控制台终端。
void tty_init(void)
{
	int i;
    // 首先初始化所有终端的缓冲队列结构,设置初值。对于串行终端的读/写缓冲队列,将它们的
    // data字段设置为串行端口基地址值。串口1是0x3f8,串口2是0x2f8。然后先初步设置所有
    // 终端的tty结构。其中特殊字符数组c_cc[]设置的初值定义在include/linux/tty.h文件中。
	for (i=0 ; i < QUEUES ; i++)
		tty_queues[i] = (struct tty_queue) {0,0,0,0,""};
		
		
	rs_queues[0] = (struct tty_queue) {0x3f8,0,0,0,""};
	rs_queues[1] = (struct tty_queue) {0x3f8,0,0,0,""};
	rs_queues[3] = (struct tty_queue) {0x2f8,0,0,0,""};
	rs_queues[4] = (struct tty_queue) {0x2f8,0,0,0,""};
	for (i=0 ; i<256 ; i++) {
		tty_table[i] =  (struct tty_struct) {
		 	{0, 0, 0, 0, 0, INIT_C_CC},
			0, 0, 0, NULL, NULL, NULL, NULL
		};
	}
	
	// 接着初始化控制台终端(console.c,834行)。把 con_init()放在这里,是因为我们需要根
    // 据显示卡类型和显示内存容量来确定系统中虚拟控制台的数量NR_CONSOLES。该值被用于随后
    // 的控制台tty 结构初始化循环中。对于控制台的tty 结构,425--430行是tty结构中包含的
    // termios结构字段。其中输入模式标志集被初始化为ICRNL标志;输出模式标志被初始化为含
    // 有后处理标志OPOST和把NL转换成CRNL的标志ONLCR;本地模式标志集被初始化含有IXON、
    // ICANON、ECHO、ECHOCTL和ECHOKE标志;控制字符数组c_cc[]被设置含有初始值INIT_C_CC。
    // 435行上初始化控制台终端tty结构中的读缓冲、写缓冲和辅助缓冲队列结构,它们分别指向
    // tty 缓冲队列结构数组tty_table[]中的相应结构项。参见61--73行上的相关说明。
	con_init();
	for (i = 0 ; i<NR_CONSOLES ; i++) {
		con_table[i] = (struct tty_struct) {
		 	{ICRNL,					/* CR转NL */
			OPOST|ONLCR,			/*NL转CRNL*/
			0,						// 控制模式标志集。
			IXON | ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,	// 本地标志集。
			0,		/* console termio */		// 线路规程,0 -- TTY。
			INIT_C_CC},							// 控制字符数组c_cc[]。
			0,			/* initial pgrp */		// 所属初始进程组pgrp。
			0,			/* initial session */	// 初始会话组session。
			0,			/* initial stopped */	// 初始停止标志stopped。
			con_write,							// 控制台写函数。
			con_queues+0+i*3,con_queues+1+i*3,con_queues+2+i*3
		};
	}
	// 然后初始化串行终端的tty结构各字段。450行初始化串行终端tty 结构中的读/写和辅助缓
    // 冲队列结构,它们分别指向tty 缓冲队列结构数组tty_table[]中的相应结构项。参见61--
    // 73行上的相关说明。
	for (i = 0 ; i<NR_SERIALS ; i++) {
		rs_table[i] = (struct tty_struct) {
			{0, /* no translation */		// 输入模式标志集。0,无须转换。
			0,  /* no translation */		// 输出模式标志集。0,无须转换。
			B2400 | CS8,					// 控制模式标志集。2400bps,8位数据位。
			0,					// 本地模式标志集。
			0,					// 线路规程,0 -- TTY。
			INIT_C_CC},			// 控制字符数组。
			0,					// 所属初始进程组。
			0,					// 初始会话组。
			0,					// 初始停止标志。
			rs_write,			// 串口终端写函数。
			rs_queues+0+i*3,rs_queues+1+i*3,rs_queues+2+i*3 // 三个队列。
		};
	}
	
	// 然后再初始化伪终端使用的tty结构。伪终端是配对使用的,即一个主(master)伪终端配
    // 有一个从(slave)伪终端。因此对它们都要进行初始化设置。在循环中,我们首先初始化
    // 每个主伪终端的tty结构,然后再初始化其对应的从伪终端的tty结构。
	for (i = 0 ; i<NR_PTYS ; i++) {
		mpty_table[i] = (struct tty_struct) {
			{0, /* no translation */		// 输入模式标志集。0,无须转换。
			0,  /* no translation */		// 输出模式标志集。0,无须转换。
			B9600 | CS8,					// 控制模式标志集。9600bps,8位数据位。
			0,			// 本地模式标志集。
			0,			// 线路规程,0 -- TTY。
			INIT_C_CC},	// 控制字符数组。
			0,			// 所属初始进程组。
			0,			// 所属初始会话组。
			0,			// 初始停止标志。
			mpty_write,	// 主伪终端写函数。
			mpty_queues+0+i*3,mpty_queues+1+i*3,mpty_queues+2+i*3
		};
		spty_table[i] = (struct tty_struct) {
			{0, /* no translation */		// 输入模式标志集。0,无须转换。
			0,  /* no translation */		// 输出模式标志集。0,无须转换。
			B9600 | CS8,					// 控制模式标志集。9600bps,8位数据位。
			IXON | ISIG | ICANON,		// 本地模式标志集。
			0,				// 线路规程,0 -- TTY。
			INIT_C_CC},		// 控制字符数组。
			0,				// 所属初始进程组。
			0,				// 所属初始会话组。
			0,				// 初始停止标志。
			spty_write,		// 从伪终端写函数。
			spty_queues+0+i*3,spty_queues+1+i*3,spty_queues+2+i*3
		};
	}
	// 最后初始化串行中断处理程序和串行接口1和2(serial.c,37行),并显示系统含有的虚拟
    // 控制台数NR_CONSOLES 和伪终端数NR_PTYS。
	rs_init();
	printk("%d virtual consoles\n\r",NR_CONSOLES);
	printk("%d pty's\n\r",NR_PTYS);
}

10-6-3 控制字符VTIME、VMIN

在非规范模式下,这两个值是超时定时值和最小读取字符个数。MIN表示为了满足读操作,
需要读取的最少字符数。TIME是一个十分之一秒计数的超时计时值。
当这两个都设置的话,读操作将等待,直到至少读到一个字符,如果在超时之前收到MIN个字符,则读操作即被满足。如果在MIN个字符被收到之前就已超时,就将到此时已收到的字符返回给用户。
如果仅设置了MIN,那么在读取MIN个字符之前读操作将不返回。
如果仅设置了TIME,那么在读到至少一个字符或者定时超时后读操作将立刻返回。
如果两个都没有设置,则读操作将立刻返回,仅给出目前已读的字节数。

详细说明参见termios.h文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/568208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据可视化:部分整体类可视化图表大全

图表是处理数据的重要组成部分&#xff0c;因为它们是一种将大量数据压缩为易于理解的格式的方法。数据可视化可以让受众快速Get到重点。 数据可视化的图表类型极其丰富多样&#xff0c;而且每种都有不同的用例&#xff0c;通常&#xff0c;创建数据可视化最困难的部分是确定哪…

冯诺依曼体系结构详解

一.冯诺伊曼体系结构的概念&#xff1a; 约翰冯诺依曼&#xff08;John von Neumann&#xff0c;1903.1.28-1957.2.8&#xff09;&#xff0c;美籍匈牙利数学家&#xff0c;计算机科学家&#xff0c;物理学家。是20世纪最重要的数学家之一&#xff0c;后来被称为计算机之父。 后…

计算机网络学习笔记-网络层

目录 概述 提供的两种服务&#xff1a;面向连接的虚电路、不面向连接的数据报 对比 虚拟互连网络 地址解析协议 ARP 主要作用 使用过程 位置 因特网控制报文协议 ICMP 作用 位置 种类 差错报告报文&#xff1a;终点不可达、源点抑制、时间超过、参数问题、改变路由…

【HMS Core】【ML Kit】活体检测FAQ合集

【问题描述1】 使用示例代码集成活体检测SDK时&#xff0c;报错state code -7001 【解决方案】 使用示例代码前请详细阅读示例工程中的“README”文件。您需要完成以下操作后才可以运行示例代码。 在AppGallery Connect网站下载自己应用的“agconnect-services.json”文件&a…

kaggle新赛推荐 | 从游戏中预测学生的表现

赛题名称&#xff1a;Predict Student Performance from Game Play 从游戏中预测学生的表现 赛题链接&#xff1a;https://www.kaggle.com/competitions/predict-student-performance-from-game-play 赛题背景 学习意味着有趣&#xff0c;这就是基于游戏的学习的用武之地。这…

Java大型货运系统源码(司机APP端+货主APP端)

技术架构&#xff1a;spring boot、mybatis、redis、vue、element-ui 开发语言&#xff1a;java 开发工具&#xff1a;idea、vscode、hbuilder 前端框架&#xff1a;vue 后端框架&#xff1a;spring boot 数 据 库&#xff1a;mysql 移 动 端&#xff1a;uniapp混合开发原…

数据结构与算法(七)

二叉树 如果说树中的每个结点最多只能有两个子结点&#xff0c;这样的树我们就称为二叉树&#xff0c;二叉树可以为空。 特点: 每个结点最多有两棵子树&#xff0c;所以二叉树中不存在度大于二的结点棵树中&#xff0c;最大的结点的度称为树的度&#xff0c;结点的度:结点所…

Git 分支相关操作

1 创建一个分支 Create a new directory and initialize a Git repository. We are going to create a directory named “tutorial”. $ mkdir tutorial $ cd tutorial $ git init Initialized empty Git repository in /Users/eguchi/Desktop/tutorial/.git/进入这个tutori…

一篇文章全面了解光分路器、PLC分路器、拉锥分路器

光纤分路器 光纤分路器&#xff0c;又称为分光器&#xff0c;是将一根光纤信号按照既定的比例分解为两路或多路光信号输出&#xff0c;是接入FTTH方式的光无源器件。 例如&#xff0c;一个1x4光分路器就是将一根光纤中的光信号按照一定的比例分配给四根光纤。与WDM系统的波分复…

【Java入门】运算符

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Java入门篇系列&#xff0c;该专栏主要讲解&#xff1a;什么是java、java的数据类型与变…

放大镜-第14届蓝桥杯省赛Scratch中级组真题第3题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第138讲。 放大镜&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组编程第3题&#xff0…

MAC环境下使用 xray 工具

这里不做过多介绍&#xff0c;下面链接讲的非常清楚&#xff0c;下面记录一下遇到的坑。 https://docs.xray.cool/#/tutorial/webscan_basic_crawler Mac环境下选择对应的工具 下载完以后&#xff0c;放入自己的目录下&#xff0c;打开终端查看版本信息 ./xray_darwin_amd64 v…

Jupyter程序安装和使用指南【操作示例】

Jupyter Notebook(简称Jupyter)是一个交互式编辑器&#xff0c;它支持运行40多种编程语言&#xff0c;便于创建和共享文档。Jupyter本质上是一个Web应用程序&#xff0c;与其他编辑器相比&#xff0c;它具有小巧、灵活、支持实时代码、方便图表展示等优点。下面分别为大家演示如…

在CTEX文档生成中使用WinEit编辑带有公式符号的中文文档应用举例

CTEX文档生成中使用WinEit编辑带有公式符号的中文文档应用举例 CTEX在编辑文档格式和排版时具有优秀的性能&#xff0c;可批量处理文档格式&#xff0c;该用格式时候也非常快捷。下面举例介绍CTEX文档生成中怎样使用WinEit编辑带有公式符号的中文文档。 1.需要的代码 .在WinEi…

IT入门深似海,入门到放弃你学废了嘛

我一直觉得IT行业 程序员行业。甚至觉得程序员人群 是一个特殊存在的群体。 入门到放弃&#xff0c;是真的&#xff0c;IT门槛高嘛。 其实吧&#xff0c;IT编程门槛&#xff0c;是有的&#xff0c;但是对于感兴趣的&#xff0c;想学习IT编程同学来说&#xff0c;也是一件容易事…

Few-Shot Knowledge Graph Completion

[1911.11298] Few-Shot Knowledge Graph Completion (arxiv.org) 目录 Background Model Encoding Heterogeneous Neighbors Aggregating Few-Shot Reference Set Matching Query and Reference Set Matching Query and Reference Set Background 以往的KGC认为每个关系…

【微信小程序】微信小程序集成高德卫星地图完成多边形绘制与截图保存

目录 功能需求 使用的技术点 注意点 实现步骤 代码 微信小程序-地图所在的wxml 微信小程序-地图所在的js 微信小程序-展示截图结果的wxml 微信小程序-展示截图结果的js H5-地图所在的html 完成效果 感谢阅读&#xff0c;欢迎讨论 功能需求 打开页面展示卫星地图&…

震惊!人工智能引发灰色经济,ChatGPT变身罪魁祸首!

人工智能技术的日益发展和普及&#xff0c;其呈现出无边界的开发空间&#xff0c;引领出无数的商业应用&#xff0c;越来越多的领域开始依赖这一技术&#xff0c;各种应用场景日益丰富&#xff0c;而其内在的巨大潜力也被不断开发。随之而来的则是&#xff0c;因为技术的滥用和…

13 张图,带你深入理解Synchronized,吊打所有大厂面试官

前言 分享一篇优质文章给你。 本文带读者们由浅入深理解Synchronized&#xff0c;让读者们也能与面试官疯狂对线&#xff0c;同时写出高性能的代码和架构。 在并发编程中Synchronized一直都是元老级的角色&#xff0c;Jdk 1.6以前大家都称呼它为重量级锁&#xff0c;相对于J…

freertos任务优先级分配

RQ 任务&#xff1a;IRQ 任务是指通过中断服务程序进行触发的任务&#xff0c;此类任务应该设置为所有任务里面优先 级最高的。高优先级后台任务&#xff1a;比如按键检测&#xff0c;触摸检测&#xff0c;USB 消息处理&#xff0c;串口消息处理等&#xff0c;都可以归为这一类…