程序10-6 linux/kernel/chr_drv/tty_ioctl.c
1 /*
2 * linux/kernel/chr_drv/tty_ioctl.c
3 *
4 * (C) 1991 Linus Torvalds
5 */
6
7 #include <errno.h> // 错误号头文件。包含系统中各种出错号。
8 #include <termios.h> // 终端输入输出函数头文件。主要定义控制异步通信口的终端接口。
9
10 #include <linux/sched.h> // 调度程序头文件,定义任务结构task_struct、任务0的数据等。
11 #include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。
12 #include <linux/tty.h> // tty头文件,定义有关tty_io、串行通信方面参数、常数。
13
14 #include <asm/io.h> // io头文件。定义硬件端口输入/输出宏汇编语句。
15 #include <asm/segment.h> // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。
16 #include <asm/system.h> // 系统头文件。定义设置或修改描述符/中断门等的嵌入式汇编宏。
17
// 根据进程组号pgrp取得进程组所属的会话号。定义在kernel/exit.c,161行。
18 extern int session_of_pgrp(int pgrp);
// 向使用指定tty终端的进程组中所有进程发送信号。定义在chr_drv/tty_io.c,246行。
19 extern int tty_signal(int sig, struct tty_struct *tty);
20
// 这是波特率因子数组(或称为除数数组)。波特率与波特率因子的对应关系参见列表后说明。
// 例如波特率是2400bps时,对应的因子是48(0x30);9600bps的因子是12(0x1c)。
21 static unsigned short quotient[] = {
22 0, 2304, 1536, 1047, 857,
23 768, 576, 384, 192, 96,
24 64, 48, 24, 12, 6, 3
25 };
26
//// 修改传输波特率。
// 参数:tty - 终端对应的tty数据结构。
// 在除数锁存标志DLAB置位情况下,通过端口0x3f8和0x3f9向UART分别写入波特率因子低
// 字节和高字节。写完后再复位DLAB位。对于串口2,这两个端口分别是0x2f8和0x2f9。
27 static void change_speed(struct tty_struct * tty)
28 {
29 unsigned short port,quot;
30
// 函数首先检查参数tty 指定的终端是否是串行终端,若不是则退出。对于串口终端的tty结
// 构,其读缓冲队列data 字段存放着串行端口基址(0x3f8或0x2f8),而一般控制台终端的
// tty结构的read_q.data字段值为0。然后从终端termios结构的控制模式标志集中取得已设
// 置的波特率索引号,并据此从波特率因子数组quotient[]中取得对应的波特率因子值quot。
// CBAUD是控制模式标志集中波特率位屏蔽码。
31 if (!(port = tty->read_q->data))
32 return;
33 quot = quotient[tty->termios.c_cflag & CBAUD];
// 接着把波特率因子quot写入串行端口对应UART芯片的波特率因子锁存器中。在写之前我们
// 先要把线路控制寄存器 LCR 的除数锁存访问比特位DLAB(位7)置1。然后把 16位的波特
// 率因子低高字节分别写入端口 0x3f8、0x3f9 (分别对应波特率因子低、高字节锁存器)。
// 最后再复位LCR的DLAB标志位。
34 cli();
35 outb_p(0x80,port+3); /* set DLAB */ // 首先设置除数锁定标志DLAB。
36 outb_p(quot & 0xff,port); /* LS of divisor */ // 输出因子低字节。
37 outb_p(quot >> 8,port+1); /* MS of divisor */ // 输出因子高字节。
38 outb(0x03,port+3); /* reset DLAB */ // 复位DLAB。
39 sti();
40 }
41
//// 刷新tty缓冲队列。
// 参数:queue - 指定的缓冲队列指针。
// 令缓冲队列的头指针等于尾指针,从而达到清空缓冲区的目的。
42 static void flush(struct tty_queue * queue)
43 {
44 cli();
45 queue->head = queue->tail;
46 sti();
47 }
48
//// 等待字符发送出去。
49 static void wait_until_sent(struct tty_struct * tty)
50 {
51 /* do nothing - not implemented */ /* 什么都没做 - 还未实现 */
52 }
53
//// 发送BREAK控制符。
54 static void send_break(struct tty_struct * tty)
55 {
56 /* do nothing - not implemented */ /* 什么都没做 - 还未实现 */
57 }
58
//// 取终端termios结构信息。
// 参数:tty - 指定终端的tty结构指针;termios - 存放termios结构的用户缓冲区。
59 static int get_termios(struct tty_struct * tty, struct termios * termios)
60 {
61 int i;
62
// 首先验证用户缓冲区指针所指内存区容量是否足够,如不够则分配内存。然后复制指定终端
// 的termios结构信息到用户缓冲区中。最后返回0。
63 verify_area(termios, sizeof (*termios)); // kernel/fork.c,24行。
64 for (i=0 ; i< (sizeof (*termios)) ; i++)
65 put_fs_byte( ((char *)&tty->termios)[i] , i+(char *)termios );
66 return 0;
67 }
68
//// 设置终端termios结构信息。
// 参数:tty - 指定终端的tty结构指针;termios - 用户数据区termios结构指针。
69 static int set_termios(struct tty_struct * tty, struct termios * termios,
70 int channel)
71 {
72 int i, retsig;
73
74 /* If we try to set the state of terminal and we're not in the
75 foreground, send a SIGTTOU. If the signal is blocked or
76 ignored, go ahead and perform the operation. POSIX 7.2) */
/* 如果试图设置终端的状态但此时终端不在前台,那么我们就需要发送
一个SIGTTOU信号。如果该信号被进程屏蔽或者忽略了,就直接执行
本次操作。 POSIX 7.2 */
// 如果当前进程使用的tty终端的进程组号与进程的进程组号不同,即当前进程终端不在前台,
// 表示当前进程试图修改不受控制的终端的termios结构。因此根据POSIX标准的要求这里需
// 要发送SIGTTOU信号让使用这个终端的进程先暂时停止执行,以让我们先修改termios结构。
// 如果发送信号函数tty_signal()返回值是ERESTARTSYS或EINTR,则等一会再执行本次操作。
77 if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
78 retsig = tty_signal(SIGTTOU, tty); // chr_drv/tty_io.c,246行。
79 if (retsig == -ERESTARTSYS || retsig == -EINTR)
80 return retsig;
81 }
// 接着把用户数据区中termios结构信息复制到指定终端tty结构的termios结构中。因为用
// 户有可能已修改了终端串行口传输波特率,所以这里再根据termios结构中的控制模式标志
// c_cflag中的波特率信息修改串行UART芯片内的传输波特率。最后返回0。
82 for (i=0 ; i< (sizeof (*termios)) ; i++)
83 ((char *)&tty->termios)[i]=get_fs_byte(i+(char *)termios);
84 change_speed(tty);
85 return 0;
86 }
87
//// 读取termio结构中的信息。
// 参数:tty - 指定终端的tty结构指针;termio - 保存termio结构信息的用户缓冲区。
88 static int get_termio(struct tty_struct * tty, struct termio * termio)
89 {
90 int i;
91 struct termio tmp_termio;
92
// 首先验证用户的缓冲区指针所指内存区容量是否足够,如不够则分配内存。然后将termios
// 结构的信息复制到临时 termio 结构中。这两个结构基本相同,但输入、输出、控制和本地
// 标志集数据类型不同。前者的是long,而后者的是short。因此先复制到临时termio结构
// 中目的是为了进行数据类型转换。
93 verify_area(termio, sizeof (*termio));
94 tmp_termio.c_iflag = tty->termios.c_iflag;
95 tmp_termio.c_oflag = tty->termios.c_oflag;
96 tmp_termio.c_cflag = tty->termios.c_cflag;
97 tmp_termio.c_lflag = tty->termios.c_lflag;
98 tmp_termio.c_line = tty->termios.c_line;
99 for(i=0 ; i < NCC ; i++)
100 tmp_termio.c_cc[i] = tty->termios.c_cc[i];
// 最后逐字节地把临时termio结构中的信息复制到用户termio结构缓冲区中。并返回0。
101 for (i=0 ; i< (sizeof (*termio)) ; i++)
102 put_fs_byte( ((char *)&tmp_termio)[i] , i+(char *)termio );
103 return 0;
104 }
105
106 /*
107 * This only works as the 386 is low-byt-first
108 */
/*
* 下面termio设置函数仅适用于低字节在前的386 CPU。
*/
//// 设置终端termio结构信息。
// 参数:tty - 指定终端的tty结构指针;termio - 用户数据区中termio结构。
// 将用户缓冲区termio的信息复制到终端的termios结构中。返回0 。
109 static int set_termio(struct tty_struct * tty, struct termio * termio,
110 int channel)
111 {
112 int i, retsig;
113 struct termio tmp_termio;
114
// 与set_termios()一样,如果进程使用的终端的进程组号与进程的进程组号不同,即当前进
// 程终端不在前台,表示当前进程试图修改不受控制的终端的termios结构。因此根据POSIX
// 标准的要求这里需要发送SIGTTOU信号让使用这个终端的进程先暂时停止执行,以让我们先
// 修改termios结构。如果发送信号函数tty_signal()返回值是ERESTARTSYS或EINTR,则等
// 一会再执行本次操作。
115 if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
116 retsig = tty_signal(SIGTTOU, tty);
117 if (retsig == -ERESTARTSYS || retsig == -EINTR)
118 return retsig;
119 }
// 接着复制用户数据区中termio结构信息到临时termio结构中。然后再将termio结构的信息
// 复制到tty的 termios 结构中。这样做的目的是为了对其中模式标志集的类型进行转换,即
// 从 termio的短整数类型转换成termios的长整数类型。但两种结构的c_line和c_cc[]字段
// 是完全相同的。
120 for (i=0 ; i< (sizeof (*termio)) ; i++)
121 ((char *)&tmp_termio)[i]=get_fs_byte(i+(char *)termio);
122 *(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;
123 *(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;
124 *(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;
125 *(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;
126 tty->termios.c_line = tmp_termio.c_line;
127 for(i=0 ; i < NCC ; i++)
128 tty->termios.c_cc[i] = tmp_termio.c_cc[i];
// 最后因为用户有可能已修改了终端串行口传输波特率,所以这里再根据termios结构中的控制
// 模式标志c_cflag中的波特率信息修改串行UART芯片内的传输波特率,并返回0。
129 change_speed(tty);
130 return 0;
131 }
132
//// tty终端设备输入输出控制函数。
// 参数:dev - 设备号;cmd - ioctl命令;arg - 操作参数指针。
// 该函数首先根据参数给出的设备号找出对应终端的tty结构,然后根据控制命令cmd分别进行
// 处理。
133 int tty_ioctl(int dev, int cmd, int arg)
134 {
135 struct tty_struct * tty;
136 int pgrp;
137
// 首先根据设备号取得tty子设备号,从而取得终端的tty结构。若主设备号是5(控制终端),
// 则进程的tty字段即是tty子设备号。此时如果进程的tty子设备号是负数,表明该进程没有
// 控制终端,即不能发出该ioctl调用,于是显示出错信息并停机。如果主设备号不是5而是4,
// 我们就可以从设备号中取出子设备号。子设备号可以是0(控制台终端)、1(串口1终端)、
// 2(串口2终端)。
138 if (MAJOR(dev) == 5) {
139 dev=current->tty;
140 if (dev<0)
141 panic("tty_ioctl: dev<0");
142 } else
143 dev=MINOR(dev);
// 然后根据子设备号和tty表,我们可取得对应终端的tty结构。于是让tty指向对应子设备
// 号的tty结构。然后再根据参数提供的ioctl命令cmd进行分别处理。144行后半部分用于
// 根据子设备号dev在tty_table[]表中选择对应的tty结构。如果dev = 0,表示正在使用
// 前台终端,因此直接使用终端号fg_console 作为 tty_table[] 项索引取 tty结构。如果
// dev大于0,那么就要分两种情况考虑:① dev 是虚拟终端号;② dev 是串行终端号或者
// 伪终端号。对于虚拟终端其tty结构在 tty_table[]中索引项是 dev-1(0 -- 63)。对于
// 其它类型终端,则它们的 tty结构索引项就是 dev。例如,如果dev = 64,表示是一个串
// 行终端1,则其tty 结构就是 ttb_table[dev]。 如果dev = 1,则对应终端的tty结构是
// tty_table[0]。参见tty_io.c程序第70 -- 73行。
144 tty = tty_table + (dev ? ((dev < 64)? dev-1:dev) : fg_console);
145 switch (cmd) {
// 取相应终端termios结构信息。此时参数arg是用户缓冲区指针。
146 case TCGETS:
147 return get_termios(tty,(struct termios *) arg);
// 在设置termios结构信息之前,需要先等待输出队列中所有数据处理完毕,并且刷新(清空)
// 输入队列。再接着执行下面设置终端termios结构的操作。
148 case TCSETSF:
149 flush(tty->read_q); /* fallthrough */ /* 接着继续执行 */
// 在设置终端termios的信息之前,需要先等待输出队列中所有数据处理完(耗尽)。对于修改
// 参数会影响输出的情况,就需要使用这种形式。
150 case TCSETSW:
151 wait_until_sent(tty); /* fallthrough */
// 设置相应终端termios结构信息。此时参数arg是保存termios结构的用户缓冲区指针。
152 case TCSETS:
153 return set_termios(tty,(struct termios *) arg, dev);
// 取相应终端termio结构中的信息。此时参数arg是用户缓冲区指针。
154 case TCGETA:
155 return get_termio(tty,(struct termio *) arg);
// 在设置termio 结构信息之前,需要先等待输出队列中所有数据处理完毕,并且刷新(清空)
// 输入队列。再接着执行下面设置终端termio 结构的操作。
156 case TCSETAF:
157 flush(tty->read_q); /* fallthrough */ /* 接着继续执行 */
// 在设置终端termios的信息之前,需要先等待输出队列中所有数据处理完(耗尽)。对于修改
// 参数会影响输出的情况,就需要使用这种形式。
158 case TCSETAW:
159 wait_until_sent(tty); /* fallthrough */
// 设置相应终端termio结构信息。此时参数arg是保存termio结构的用户缓冲区指针。
160 case TCSETA:
161 return set_termio(tty,(struct termio *) arg, dev);
// 如果参数arg值是0,则等待输出队列处理完毕(空),并发送一个break。
162 case TCSBRK:
163 if (!arg) {
164 wait_until_sent(tty);
165 send_break(tty);
166 }
167 return 0;
// 开始/停止流控制。如果参数arg是TCOOFF(Terminal Control Output OFF),则挂起输出;
// 如果是 TCOON,则恢复挂起的输出。在挂起或恢复输出同时需要把写队列中的字符输出,以
// 加快用户交互响应速度。如果arg是TCIOFF(Terminal Control Input ON),则挂起输入;
// 如果是TCION,则重新开启挂起的输入。
168 case TCXONC:
169 switch (arg) {
170 case TCOOFF:
171 tty->stopped = 1; // 停止终端输出。
172 tty->write(tty); // 写缓冲队列输出。
173 return 0;
174 case TCOON:
175 tty->stopped = 0; // 恢复终端输出。
176 tty->write(tty);
177 return 0;
// 如果参数arg是TCIOFF,表示要求终端停止输入,于是我们往终端写队列中放入STOP字符。
// 当终端收到该字符时就会暂停输入。如果参数是TCION,表示要发送一个START字符,让终
// 端恢复传输。
// STOP_CHAR(tty)定义为 ((tty)->termios.c_cc[VSTOP]),即取终端termios结构控制字符数
// 组对应项值。若内核定义了_POSIX_VDISABLE(\0),那么当某一项值等于_POSIX_VDISABLE
// 的值时,表示禁止使用相应的特殊字符。因此这里直接判断该值是否为0 来确定要不要把停
// 止控制字符放入终端写队列中。以下同。
178 case TCIOFF:
179 if (STOP_CHAR(tty))
180 PUTCH(STOP_CHAR(tty),tty->write_q);
181 return 0;
182 case TCION:
183 if (START_CHAR(tty))
184 PUTCH(START_CHAR(tty),tty->write_q);
185 return 0;
186 }
187 return -EINVAL; /* not implemented */
// 刷新已写输出但还没有发送、或已收但还没有读的数据。如果参数arg是0,则刷新(清空)
// 输入队列;如果是1,则刷新输出队列;如果是2,则刷新输入和输出队列。
188 case TCFLSH:
189 if (arg==0)
190 flush(tty->read_q);
191 else if (arg==1)
192 flush(tty->write_q);
193 else if (arg==2) {
194 flush(tty->read_q);
195 flush(tty->write_q);
196 } else
197 return -EINVAL;
198 return 0;
// 设置终端串行线路专用模式。
199 case TIOCEXCL:
200 return -EINVAL; /* not implemented */ /* 未实现 */
// 复位终端串行线路专用模式。
201 case TIOCNXCL:
202 return -EINVAL; /* not implemented */
// 设置tty为控制终端。(TIOCNOTTY - 不要控制终端)。
203 case TIOCSCTTY:
204 return -EINVAL; /* set controlling term NI */ /* 未实现 */
// 读取终端进程组号(即读取前台进程组号)。 首先验证用户缓冲区长度,然后复制终端tty
// 的pgrp字段到用户缓冲区。此时参数arg是用户缓冲区指针。
205 case TIOCGPGRP: // 实现库函数tcgetpgrp()。
206 verify_area((void *) arg,4);
207 put_fs_long(tty->pgrp,(unsigned long *) arg);
208 return 0;
// 设置终端进程组号pgrp(即设置前台进程组号)。 此时参数arg 是用户缓冲区中进程组号
// pgrp 的指针。执行该命令的前提条件是进程必须有控制终端。 如果当前进程没有控制终端,
// 或者dev 不是其控制终端,或者控制终端现在的确是正在处理的终端dev,但进程的会话号
// 与该终端dev的会话号不同,则返回无终端错误信息。
209 case TIOCSPGRP: // 实现库函数tcsetpgrp()。
210 if ((current->tty < 0) ||
211 (current->tty != dev) ||
212 (tty->session != current->session))
213 return -ENOTTY;
// 然后我们就从用户缓冲区中取得欲设置的进程组号,并对该组号的有效性进行验证。如果组
// 号pgrp小于0,则返回无效组号错误信息;如果 pgrp的会话号与当前进程的不同,则返回
// 许可错误信息。否则我们可以设中终端的进程组号为prgp。此时prgp成为前台进程组。
214 pgrp=get_fs_long((unsigned long *) arg);
215 if (pgrp < 0)
216 return -EINVAL;
217 if (session_of_pgrp(pgrp) != current->session)
218 return -EPERM;
219 tty->pgrp = pgrp;
220 return 0;
// 返回输出队列中还未送出的字符数。首先验证用户缓冲区长度,然后复制队列中字符数给用户。
// 此时参数arg是用户缓冲区指针。
221 case TIOCOUTQ:
222 verify_area((void *) arg,4);
223 put_fs_long(CHARS(tty->write_q),(unsigned long *) arg);
224 return 0;
// 返回输入队列中还未读取的字符数。首先验证用户缓冲区长度,然后复制队列中字符数给用户。
// 此时参数arg是用户缓冲区指针。
225 case TIOCINQ:
226 verify_area((void *) arg,4);
227 put_fs_long(CHARS(tty->secondary),
228 (unsigned long *) arg);
229 return 0;
// 模拟终端输入操作。该命令以一个指向字符的指针作为参数,并假设该字符是在终端上键入的。
// 用户必须在该控制终端上具有超级用户权限或具有读许可权限。
230 case TIOCSTI:
231 return -EINVAL; /* not implemented */ /* 未实现 */
// 读取终端设备窗口大小信息(参见termios.h中的winsize结构)。
232 case TIOCGWINSZ:
233 return -EINVAL; /* not implemented */
// 设置终端设备窗口大小信息(参见winsize结构)。
234 case TIOCSWINSZ:
235 return -EINVAL; /* not implemented */
// 返回MODEM状态控制引线的当前状态比特位标志集(参见termios.h中185 -- 196行)。
236 case TIOCMGET:
237 return -EINVAL; /* not implemented */
// 设置单个modem状态控制引线的状态(true或false)。
238 case TIOCMBIS:
239 return -EINVAL; /* not implemented */
// 复位单个MODEM状态控制引线的状态。
240 case TIOCMBIC:
241 return -EINVAL; /* not implemented */
// 设置MODEM状态引线的状态。如果某一比特位置位,则modem对应的状态引线将置为有效。
242 case TIOCMSET:
243 return -EINVAL; /* not implemented */
// 读取软件载波检测标志(1 - 开启;0 - 关闭)。
244 case TIOCGSOFTCAR:
245 return -EINVAL; /* not implemented */
// 设置软件载波检测标志(1 - 开启;0 - 关闭)。
246 case TIOCSSOFTCAR:
247 return -EINVAL; /* not implemented */
248 default:
249 return -EINVAL;
250 }
251 }
252