这是对linux的模仿,在linux中所有的设备都是文件,在前面已经做了准备工作,在根目录中有3个TTY文件,它们的i结点的i_start_sec字段就填充为TTY号。同时我也不想允许随意的无意义的输入了。我要以进程的角度来操纵TTY,所有对TTY的操纵都必须通过的TTY文件的Write_File和Read_File接口来实现。
先来比较简单的,写TTY。在Do_Read_Write_File函数中添加如下语句:
kernel/fs.c
- /* 如果是要读写TTY,发一个消息TTY消息处理进程 */
- if(p_inode->i_mode & INODE_MODE_SPECIAL)
- {
- m->msg_type = (m->msg_type == FS_READ_FILE) ? TTY_READ : TTY_WRITE;
- m->i1 = m->i2; /* 要读写的字节数 */
- m->i2 = m->src_proc_pid; /* 要读写TTY的用户进程的PID */
- m->i3 = p_inode->i_start_sec; /* 要读写的TTY的TTY索引 */
- Send_Receive_Shell(BOTH,PROC_TTY_HANDLE_PID,m); /* 发送消息给TTY消息进程,并等待回应 */
- return m->r1;
- }
我们这里添加了一个TTY消息处理进程,这与书中不一样,书中的方法虽然节省了一个进程的开销,但我觉得思路不清晰。。添加一个进程的琐碎的事儿不赘述,来看进程体:
kernel/tty.c
- /*----------------------------------------------------------Proc_TTY_Msg_Handler
- TTY消息处理进程执行体
- */
- void Proc_TTY_Msg_Handler()
- {
- Message m;
- TTY *tty;
- while(1)
- {
- Send_Receive_Shell(RECEIVE,ANY,&m);
- tty = &TTY_Table[m.i3]; /* 取得要读写的TTY的指针 */
- switch(m.msg_type)
- {
- /* 读TTY */
- case TTY_READ:
- Do_TTY_Read(tty,&m);
- /* 向FS进程发送一个进程,解除FS进程的阻塞,保持始作俑者进程阻塞 */
- m.msg_type = SUSPEND_PROC;
- Send_Receive_Shell(SEND,m.src_proc_pid,&m);
- break;
- /* 写TTY */
- case TTY_WRITE:
- Do_TTY_Write(tty,&m);
- Send_Receive_Shell(SEND,m.src_proc_pid,&m);
- break;
- default:
- Panic("Unknown MSG Type/n");
- break;
- }
- }
- }
接着跟进到Do_TTY_Write函数中:
kernel/Do_TTY_Write
- /*------------------------------------------------------------------Do_TTY_Write
- 写TTY的功能函数
- */
- static void Do_TTY_Write(TTY *tty,Message *m)
- {
- int left_byte_num = m->i1; /* 取出要写入TTY的数据的字节数 */
- int already_byte_num = 0; /* 已经写入的字节数,待返回 */
- u8 *buf = m->p1; /* 取出要写的数据的缓冲区首址 */
- /* 写到没得写 */
- while(left_byte_num > 0)
- {
- Out_Char(tty->console,buf[already_byte_num]); /* 写进相应的控制台 */
- left_byte_num--;
- already_byte_num++;
- }
- m->r1 = already_byte_num; /* 返回 */
- }
过程简单吧,回顾以下流程:某用户进程使用Write_File函数,发送消息给FS进程并阻塞等待回应,FS进程接收消息,在Do_Read_Write_File把消息加工后又发给TTY消息处理进程,同时FS进程阻塞等待回应,TTY消息处理进程接收到消息,执行完写入后回复一个消息给FS进程,FS进城收到消息后解除阻塞又回复一个消息给用户进程,用户进程接收到消息解除阻塞,然后继续执行。
读TTY就没那么简单了,首先我们不想让TTY在没有执行TTY_Read函数就接收输入,那么用一个简单的方法,先设置一个全局变量:
include/global.h
- extern int Is_Read_In_Buf; /* 控制按下键盘时是否把扫描码送入键盘缓冲区 */
kernel/global.c
- int Is_Read_In_Buf = 0; /* 控制按下键盘时是否把扫描码送入键盘缓冲区 */
修改键盘中断处理程序,加一个判断:
kernel/keyboard.c
- /*--------------------------------------------------------------Keyboard_Handler
- 键盘中断处理程序
- */
- static void Keyboard_Handler(int int_vec_no)
- {
- u8 scan_code;
- scan_code = In_Byte(IN_BUFFER_8042); /* 读取扫描码到scan_code中 */
- /* 如果允许把扫描码存入缓冲区的话 */
- if(Is_Read_In_Buf)
- {
- /* 如果缓冲区还有空间,则把扫描码放入缓冲区队尾指示的位置 */
- if(KB_Input_Buf.size < BUFFER_SIZE)
- {
- *KB_Input_Buf.tail = scan_code; /* 放到队尾 */
- KB_Input_Buf.tail++; /* 尾指针前进一个位置 */
- /* 如果尾指针超出缓冲区,则回到缓冲区首地址 */
- if(KB_Input_Buf.tail == KB_Input_Buf.buffer + BUFFER_SIZE)
- {
- KB_Input_Buf.tail = KB_Input_Buf.buffer;
- }
- KB_Input_Buf.size++; /* 缓冲区大小加1 */
- }
- else
- {
- /* 没有空间则直接忽略 */
- }
- }
- }
好,这样再输入系统就没反应。
下一步就来在TTY消息处理进程中处理写TTY的消息了,则函数返回后还要向FS进程发一个SUSPEND消息,这个后面再说:
kernel/tty.c
- /*-------------------------------------------------------------------Do_TTY_Read
- 读TTY的功能函数
- */
- static void Do_TTY_Read(TTY *tty,Message *m)
- {
- /* 保持原子性,害怕在tty->tty_left_byte_num = m->i1;执行完后被打断 */
- Disable_Int();
- tty->tty_caller_pid = m->src_proc_pid; /* 存放发送进程的PID,这里是FS进程号 */
- tty->tty_user_pid = m->i2; /* 存放调用Write_File函数的进程的PID */
- tty->tty_buf = m->p1; /* 用户进程存放输入字符的缓冲区首址 */
- tty->tty_left_byte_num = m->i1; /* 存放写入字符的个数 */
- tty->tty_already_byte_num = 0; /* 已写入的字符的个数 */
- Is_Read_In_Buf = 1; /* 允许输入 */
- Enable_Int();
- }
可以看到TTY结构添加了几个字段,这是为了与TTY进程通讯设置的,新的TTY结构如下:
include/tty.h
- /* TTY结构定义 */
- typedef struct s_tty
- {
- u32 tty_buffer[TTY_BUFFER_SIZE]; /* 每个TTY有一个缓冲区,循环队列,存放解析过的32位码 */
- u32 *tty_tail; /* 尾指针 */
- u32 *tty_head; /* 头指针 */
- u32 tty_buffer_size; /* 当前缓冲区中有多少解析过的码 */
- int tty_caller_pid; /* 存放发送者的PID */
- int tty_user_pid; /* 存放始作俑者的PID */
- void *tty_buf; /* 存放缓存地址 */
- int tty_left_byte_num; /* 存放请求读写的字符个数 */
- int tty_already_byte_num; /* 存放已读写的字符个数 */
- struct s_console *console; /* 每个TTY对应的控制台指针 */
- }TTY;
那么也要对相应字段初始化,修改Init_TTY函数中:
- /*----------------------------------------------------------------------Init_TTY
- 初始化一个TTY,仅在当前文件中调用
- */
- static void Init_TTY(TTY *tty)
- {
- /* 尾指针和头指针都指向缓冲区起始处 */
- tty->tty_tail = tty->tty_head = tty->tty_buffer;
- tty->tty_buffer_size = 0; /* 缓冲区码的数目开始为0 */
- /* 初始化 */
- tty->tty_caller_pid = -1;
- tty->tty_user_pid = -1;
- tty->tty_buf = 0;
- tty->tty_left_byte_num = 0;
- tty->tty_already_byte_num = 0;
- /* 初始化控制台 */
- Init_Console(tty);
- }
Is_Read_In_Buf打开了,用户可以输入了,那么该是TTY工作的时候了,把:
kernel/tty.c
- /*----------------------------------------------------------------------Proc_TTY
- 终端显示进程
- */
- void Proc_TTY()
- {
- //Panic("PROC TTY PANIC");
- TTY *tty;
- /* 死循环 */
- while(1)
- {
- /* 遍历每个TTY */
- for(tty = TTY_Table;tty < TTY_Table + TTY_NUM;tty++)
- {
- /* 如果某TTY中要读写,则执行之 */
- if(tty->tty_left_byte_num > 0)
- {
- TTY_Read(tty);
- TTY_Write(tty);
- }
- }
- }
- }
新添加的就是在第15行的判断,TTY_Read函数没有改变,TTY_Write则有改变,就是把输入的字符打印出来,并存到始作俑者进程的缓冲区中:
kernel/tty.c
- /*---------------------------------------------------------------------TTY_Write
- 从参数指示的TTY的BUFFER读出key来到相应的控制台上,仅在当前文件中调用
- */
- static void TTY_Write(TTY *tty)
- {
- char key;
- /* 如果参数指示的TTY的缓冲区大小大于0则读出一个码给Out_Char处理 */
- if(tty->tty_buffer_size > 0)
- {
- /* 如果当前TTY有数据要写,则继续 */
- if(tty->tty_left_byte_num > 0)
- {
- /* 从解析码缓冲区中读一个出来处理 */
- Disable_Int();
- key = (char)(*tty->tty_head);
- tty->tty_head++;
- if(tty->tty_head >= tty->tty_buffer + TTY_BUFFER_SIZE)
- {
- tty->tty_head = tty->tty_buffer;
- }
- tty->tty_buffer_size--;
- Enable_Int();
- /* 如果是可显示的字符 */
- if(key >= ' ' && key <= '~')
- {
- Out_Char(tty->console,key); /* 打印出来 */
- Memory_Copy(tty->tty_buf++,&key,1); /* 复制到缓冲区中 */
- tty->tty_left_byte_num--;
- tty->tty_already_byte_num++;
- }
- /* 如果是back键按下且可以回退 */
- else if(key == '/b' && tty->tty_already_byte_num != 0)
- {
- Out_Char(tty->console,key); /* 打印出来 */
- /* 反方向 */
- tty->tty_left_byte_num++;
- tty->tty_already_byte_num--;
- }
- /* 如果是回车键按下或者要读的字符数为0 */
- if((key == '/n' || tty->tty_left_byte_num == 0))
- {
- Is_Read_In_Buf = 0; /* 不允许输入 */
- Out_Char(tty->console,'/n'); /* 打印回车 */
- /* 给FS进程发送一个消息,让他通知始作俑者进程解除阻塞 */
- Message resume_msg;
- resume_msg.msg_type = RESUME_PROC;
- resume_msg.r1 = tty->tty_already_byte_num; /* 待返回的值 */
- resume_msg.i1 = tty->tty_user_pid; /* 始作俑者进程号 */
- Send_Receive_Shell(SEND,tty->tty_caller_pid,&resume_msg);
- tty->tty_left_byte_num = 0; /* 确认工作 */
- }
- }
- }
- }
接下来看FS进程执行体:
kernel/fs.c
- /*--------------------------------------------------------------Proc_File_System
- 文件系统进程的执行体
- */
- void Proc_File_System()
- {
- Init_FS();
- Make_FS();
- Read_Super_Block(FS_DEV); /* 把超级块存到缓冲区中 */
- /* 得到FS分区的超级块打印它的标识 */
- Super_Block *sb = Get_Super_Block(FS_DEV);
- Printf("%x/n",sb->identity);
- /* 得到根目录文件的i结点,打印此i结点的模式 */
- root_dir_inode = Get_I_Node(FS_DEV,1);
- Printf("ROOT DIR IMODE:%d/n",root_dir_inode->i_mode);
- /* 索引根目录文件,打印它的i结点的索引 */
- Printf("%d/n",Search_File("/."));
- /* 接收消息 */
- while(1)
- {
- Message m;
- Send_Receive_Shell(RECEIVE,ANY,&m);
- switch(m.msg_type)
- {
- /* 打开文件消息,则交给Do_Open_File函数处理 */
- case FS_OPEN_FILE:
- m.r1 = Do_Open_File(&m);
- break;
- /* 关闭文件消息,交给Do_Close_File函数处理 */
- case FS_CLOSE_FILE:
- Do_Close_File(&m);
- break;
- /* 读写文件消息,交给Do_Read_Write_File函数处理 */
- case FS_READ_FILE:
- case FS_WRITE_FILE:
- m.r1 = Do_Read_Write_File(&m);
- /* 如果是写TTY消息,改变m.src_proc_pid为始作俑者进程 */
- if(m.msg_type == TTY_WRITE)
- {
- m.src_proc_pid = m.i2;
- }
- break;
- /* 删除文件消息,交给Do_Delete_File函数处理 */
- case FS_DEL_FILE:
- m.r1 = Do_Delete_File(&m);
- break;
- /* 解除阻塞消息 */
- case RESUME_PROC:
- m.src_proc_pid = m.i1;
- break;
- default:
- Panic("UNKNOWN MSG TYPE!/n");
- break;
- }
- /* 如果不是SUSPEND_PROC消息则回复发送者消息 */
- if(m.msg_type != SUSPEND_PROC)
- {
- Send_Receive_Shell(SEND,m.src_proc_pid,&m);
- }
- }
- }
里面涉及了SUSPEND_PROC和RESUME_PROC消息的处理,在最后再过一遍。
用到的宏如下:
include/const.h
- #define TTY_READ 11 /* 读TTY */
- #define TTY_WRITE 12 /* 写TYY */
- #define SUSPEND_PROC 13 /* 阻塞进程消息 */
- #define RESUME_PROC 14 /* 恢复进程消息 */
include/proc.h
- #define PROC_TTY_HANDLE_PID 7
再把整个读TTY的过程叙述一遍,始作俑者用户进程调用Read_File函数给FS进程一个消息并阻塞主等待用户输入,FS再给TTY消息处理进程发消息并阻塞,TTY消息处理进程修改要读的TTY的各字段,并允许输入,TTY进程此刻就不断监控着输入并完成打印输入的内容并把输入的内容存放到用户进程的缓冲区中。但鬼知道用户还需要输入多长时间,所以我们不应该让FS进程阻塞住,所以在Do_TTY_Read函数执行完后向FS进程发送一个SUSPEND_PROC消息让FS进程解除阻塞,同时保持用户进程阻塞,因为输入尚未结束。当用户输入完毕后TTY进程向FS进程发送一个RESUME_PROC让FS进程给用户进程发一个消息解除用户进程的阻塞,不能直接向用户进程发送消息,因为用户进程等待的是FS进程的消息。
OK,那么PCB的tty_bind字段就不需要了,对其初始化的代码也可以删除了。
下面是测试时间了,修改进程A的执行体:
- /*------------------------------------------------------------------------Proc_A
- 进程A的执行体
- */
- void Proc_A()
- {
- int fd = -1;
- fd = Open("/tty0",O_RDWR);
- Assert(fd != -1);
- char read_buf[50];
- int read_len;
- while(1)
- {
- read_len = Read_File(fd,read_buf,50);
- read_buf[read_len] = '/0';
- if(Str_Cmp(read_buf,"hello"))
- {
- Write_File(fd,"Hello World!/n",13);
- }
- else
- {
- if(Str_Len(read_buf) != 0)
- {
- Write_File(fd,"{",1);
- Write_File(fd,read_buf,read_len);
- Write_File(fd,"}/n",2);
- }
- }
- }
- }
在这里我们先打开一个TTY文件,指定的是TTY0,然后不断的读TTY,一次最多读50个字符,读到后跟hello比较一下,如果一致则输出Hello World!反之则在两边加中括号并打印出来。
make,bochs,运行结果如下:
这个就是以后的SHELL的雏形,第9章也接近尾声了。。