5.设备中断
一.设置中断
1.start.c\start()
// 默认所有trap在机器模式下处理
// 这里将trap的处理托管给Supervisor mode
w_medeleg(0xffff);
w_mideleg(0xffff);
// 设置SIE寄存器接收外部,软件和定时器中断,
w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);
// 初始化定时器
timerinit();
2.main.c\main()
void main(){
if(cpuid() == 0){
consoleinit();
...
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
...
} else {
...
plicinithart(); // ask PLIC for device interrupts
}
scheduler();
}
3.console.c\consoleinit()
void consoleinit(void)
{
initlock(&cons.lock, "cons");
uartinit();
// connect read and write system calls
// to consoleread and consolewrite.
devsw[CONSOLE].read = consoleread;
devsw[CONSOLE].write = consolewrite;
}
4.uart.c\uartinit()
运行完这个函数,UART就可以产生中断了。但是因为还没有对PLIC编程,所以中断不能被CPU感知, PLIC的初始化函数plicinit()
在main()
中被调用
//初始化UART
void uartinit(void)
{
// 关闭中断
WriteReg(IER, 0x00);
// 设置波特率
WriteReg(LCR, LCR_BAUD_LATCH);
WriteReg(0, 0x03);
WriteReg(1, 0x00);
// 设置字符长度为8bit
WriteReg(LCR, LCR_EIGHT_BITS);
// 重置并打开FIFO
WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
// 重新打开中断
WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
initlock(&uart_tx_lock, "uart");
}
5.plic.c\plicinit()
PLIC与外设一样,也占用了一个I/O地址0xC000_0000
。
void plicinit(void)
{
//启动UART的中断,设置好PLIC接收哪些中断,将中断路由到CPU
*(uint32*)(PLIC + UART0_IRQ*4) = 1;
//设置PLIC接收来自I/O磁盘的中断,这节课不会介绍这部分内容。
*(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1;
}
6.plic.c\plicinithart()
plicinit()
只由0号CPU运行,但是每个CPU核都需要运行plicinithart()
,表明自己可响应哪些外设中断。
void plicinithart(void)
{
int hart = cpuid();
//每个CPU核都表明对来自UART和VIRTIO的中断感兴趣
*(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ);
//因为我们忽略中断的优先级,所以将优先级设置为0
*(uint32*)PLIC_SPRIORITY(hart) = 0;
}
到目前为止,有了生成中断的外部设备,有了PLIC可以传递中断到单个CPU。但是CPU本身还不能接收中断,因为没有设置SSTATUS寄存器。在main函数的最后,程序调用了scheduler()函数
7.riscv.h\intr_on()
scheduler()中每个进程都会执行intr_on()
,这个函数设置sstatus
接收中断
static inline void intr_on(){
w_sstatus(r_sstatus() | SSTATUS_SIE);
}
到这里,中断被完全打开了。如果PLIC正好有pending的中断,那么会有CPU核收到中断
二.UART驱动的top部分
以打印$到Console为例
1.init.c\main()
这是系统启动后运行的第一个进程
int
main(void)
{
int pid, wpid;
if(open("console", O_RDWR) < 0){
//创建console设备文件
mknod("console", CONSOLE, 0);
open("console", O_RDWR);
}
//dup(): 创建一个文件描述符,和给定fd指向同一个文件
//通过dup创建stdout和stderr
//复制文件描述符0,得到了另外两个文件描述符1,2
//最终文件描述符0,1,2都指向Console。
dup(0); // stdout
dup(0); // stderr
for(;;){
...
if(pid == 0){
exec("sh", argv);
printf("init: exec sh failed\n");
exit(1);
}
...
}
}
2. sh.c
Shell程序首先打开文件描述符0,1,2。之后向文件描述符2
写入**$ ** 。
int getcmd(char *buf, int nbuf){
// write(int fd, char *buf, int n)
// 系统调用,将buf中的n byte写到fd中,返回n
write(2, "$ ", 2);
...
}
3.sysfile.c\sys_write()
uint64 sys_write(void){
struct file *f;
int n;
uint64 p;
argaddr(1, &p);
argint(2, &n);
if(argfd(0, 0, &f) < 0)
return -1;
return filewrite(f, p, n);
}
4.file.c\filewrite()
int filewrite(struct file *f, uint64 addr, int n) {
int r, ret = 0;
if(f->writable == 0)
return -1;
if(f->type == FD_PIPE){
ret = pipewrite(f->pipe, addr, n);
} else if(f->type == FD_DEVICE){
// mknod生成的文件描述符属于设备(FD_DEVICE)
if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
return -1;
// 根据设备类型调用不同的write()函数
ret = devsw[f->major].write(1, addr, n);
} else if(f->type == FD_INODE){
...
return ret;
}
我们现在的设备是Console,所以这里会调用console.c中的consolewrite函数
5.console.c\consolewrite()
consolewrite()
是一个UART驱动的top部分
uart.c\uartputc()
函数完成实际打印字符操作。
// user_src 指明地址是否来自用户空间
int consolewrite(int user_src, uint64 src, int n)
{
int i;
for(i = 0; i < n; i++)
{
char c;
// 通过either_copyin将字符拷入,每次拷入一个字符
if(either_copyin(&c, user_src, src+i, 1) == -1)
break;
// uartputc()将字符写入给UART设备
uartputc(c);
}
return i;
}
6. proc.c\either_copyin()
int either_copyin(void *dst, int user_src, uint64 src, uint64 len)
{
struct proc *p = myproc();
if(user_src){
// 来自用户空间的地址需要使用用户页表拷贝
return copyin(p->pagetable, dst, src, len);
} else {
memmove(dst, (char*)src, len);
return 0;
}
}
7.uart.c\uartputc()
#define UART_TX_BUF_SIZE 32
// uart_tx_buf是一个大小为32的环形队列
char uart_tx_buf[UART_TX_BUF_SIZE];
uint64 uart_tx_w; // 写指针
uint64 uart_tx_r; // 读指针
void uartputc(int c)
{
acquire(&uart_tx_lock);
if(panicked){
for(;;) ;
}
//判断环形队列是否满
while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){
// 满了就sleep一会
// wait for uartstart() to open up space in the buffer.
sleep(&uart_tx_r, &uart_tx_lock);
}
uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;
uart_tx_w += 1;
uartstart();
release(&uart_tx_lock);
}
8. uart.c\uartstart()
将数据写入到THR(Transmission Holding Register)
发送寄存器。相当于告诉设备,有一个字节需要发送。一旦数据送到了设备,系统调用会返回,用户应用程序Shell继续执行。这里从内核返回到用户空间的机制与lec06的trap机制是一样的。
#define THR 0 //transmit holding register(for output bytes)
void uartstart()
{
while(1)
{
// buffer is empty.
if(uart_tx_w == uart_tx_r) return;
// IDLE状态就返回
if((ReadReg(LSR) & LSR_TX_IDLE) == 0) return;
int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE];
uart_tx_r += 1;
// maybe uartputc() is waiting for space in the buffer.
wakeup(&uart_tx_r);
WriteReg(THR, c);
}
}
与此同时,UART设备会将数据送出。在某个时间点,我们会收到中断,因为我们之前设置了要处理UART设备中断。接下来我们看一下,当发生中断时的实际处理流程
三.UART驱动的bottom部分
bottom部分就是来自UART的中断
1.trap.c\usertrap()
void usertrap(void)
{
...
} else if((which_dev = devintr()) != 0){
// ok
} else {
...
}
2.trap.c\devintr()
// check if it's an external interrupt or
// software interrupt,and handle it.
// returns 2 if timer interrupt,
// 1 if other device,
// 0 if not recognized.
int devintr()
{
uint64 scause = r_scause();
//通过SCAUSE寄存器判断当前中断是否是外设中断
if((scause & 0x8000000000000000L) &&
(scause & 0xff) == 9){
// this is a supervisor external interrupt, via PLIC.
// 获取中断, irq指明中断类型
int irq = plic_claim();
// UART中断
if(irq == UART0_IRQ){
uartintr();
} else if(irq == VIRTIO0_IRQ){
virtio_disk_intr();
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
...
}
3.plic.c\plic_claim()
// ask the PLIC what interrupt we should serve.
// 获取并返回中断号,UART的中断号是10。
int plic_claim(void)
{
int hart = cpuid();
int irq = *(uint32*)PLIC_SCLAIM(hart);
return irq;
}
4.uart.c\uartintr()
我们现在讨论的是向UART发送数据,还没有通过键盘输入任何数据,所以UART的接受寄存器现在为空,uartgetc()
返回-1,代码运行到uartstart()
,之前已经分析过
void uartintr(void)
{
// read and process incoming characters.
while(1){
int c = uartgetc();
if(c == -1) break;
consoleintr(c);
}
// send buffered characters.
acquire(&uart_tx_lock);
uartstart();
release(&uart_tx_lock);
}
5.uart.c\uartgetc()
int uartgetc(void)
{
if(ReadReg(LSR) & 0x01){
// input data is ready.
return ReadReg(RHR);
} else {
return -1;
}
}