一.設定中斷
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;
}
}