Skip to content

printf.c

TIP

格式化控制台输出 -- printf, panic。

#include <stdarg.h>

#include "types.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"
#include "proc.h"

TIP

volatile 确保编译器不会优化掉对 panicked 的读写操作。 这是一个全局标志,用于指示系统是否处于 panic 状态。 多个核心可能会并发地检查这个标志。

volatile int panicked = 0;

TIP

用于 printf 的锁,以防止来自不同进程或中断的并发调用导致输出交错。

static struct {
  struct spinlock lock;
  int locking;
} pr;

TIP

用于数字转换的字符映射表

static char digits[] = "0123456789abcdef";

TIP

打印一个带符号或无符号的整数。 xx: 要打印的数字,类型为 long long 以支持长整数。 base: 转换的进制 (例如, 10 表示十进制, 16 表示十六进制)。 sign: 是否为有符号数 (1 表示有符号, 0 表示无符号)。

static void
printint(long long xx, int base, int sign)
{
  char buf[20];
  int i;
  unsigned long long x;

TIP

如果是有符号数且为负,记录下符号,并将数值转为正数进行处理。 这样可以简化后续的进制转换逻辑。

  if(sign && (sign = (xx < 0)))
    x = -xx;
  else
    x = xx;

  i = 0;

TIP

通过循环取余的方式,将数字转换为指定进制的字符串。 例如,对于十进制数 123,第一次循环得到 '3',第二次得到 '2',第三次得到 '1'。 字符串是反向生成并存储在缓冲区中的 (e.g., 123 -> "321")。

  do {
    buf[i++] = digits[x % base];
  } while((x /= base) != 0);

TIP

如果原始数字是负数,在缓冲区的末尾添加负号。

  if(sign)
    buf[i++] = '-';

TIP

将缓冲区中的字符串反向打印到控制台,从而得到正确的顺序。 例如,缓冲区中的 "321-" 会被打印为 "-123"。

  while(--i >= 0)
    consputc(buf[i]);
}

TIP

打印一个 64 位指针地址 (格式为 0x... )。 RISC-V 是 64 位架构,所以指针是 64 位。

static void
printptr(uint64 x)
{
  int i;
  consputc('0');
  consputc('x');

TIP

打印 16 个十六进制位 (64位 / 4位每十六进制位 = 16)。 每次循环,我们提取 x 的最高4位来获取一个十六进制数字。

  for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4)
    consputc(digits[x >> (sizeof(uint64) * 8 - 4)]);
}

TIP

格式化输出到控制台,是内核的主要打印函数。 支持的格式: %d - 十进制整数 %ld - 长整型 %lld- 长长整型 %u - 无符号十进制整数 %lu - 无符号长整型 %llu- 无符号长长整型 %x - 十六进制整数 %lx - 十六进制长整型 %llx- 十六进制长长整型 %p - 指针 %s - 字符串 %% - 百分号'%'

int
printf(char *fmt, ...)
{
  va_list ap;
  int i, cx, c0, c1, c2, locking;
  char *s;

TIP

检查是否需要加锁。在 panic 或早期启动阶段可能不会加锁。

  locking = pr.locking;
  if(locking)
    acquire(&pr.lock);

  va_start(ap, fmt);

TIP

遍历格式化字符串 fmt

  for(i = 0; (cx = fmt[i] & 0xff) != 0; ++i){
    if(cx != '%'){
      consputc(cx);
      continue;
    }

TIP

处理 '%' 格式说明符

    ++i;
    c0 = fmt[i+0] & 0xff;
    c1 = c2 = 0;
    if(c0) c1 = fmt[i+1] & 0xff;
    if(c1) c2 = fmt[i+2] & 0xff;
    

TIP

解析 %d, %ld, %lld (有符号十进制)

    if(c0 == 'd'){
      printint(va_arg(ap, int), 10, 1);
    } else if(c0 == 'l' && c1 == 'd'){
      printint(va_arg(ap, uint64), 10, 1);
      i += 1;
    } else if(c0 == 'l' && c1 == 'l' && c2 == 'd'){
      printint(va_arg(ap, uint64), 10, 1);
      i += 2;

TIP

解析 %u, %lu, %llu (无符号十进制)

    } else if(c0 == 'u'){
      printint(va_arg(ap, int), 10, 0);
    } else if(c0 == 'l' && c1 == 'u'){
      printint(va_arg(ap, uint64), 10, 0);
      i += 1;
    } else if(c0 == 'l' && c1 == 'l' && c2 == 'u'){
      printint(va_arg(ap, uint64), 10, 0);
      i += 2;

TIP

解析 %x, %lx, %llx (十六进制)

    } else if(c0 == 'x'){
      printint(va_arg(ap, int), 16, 0);
    } else if(c0 == 'l' && c1 == 'x'){
      printint(va_arg(ap, uint64), 16, 0);
      i += 1;
    } else if(c0 == 'l' && c1 == 'l' && c2 == 'x'){
      printint(va_arg(ap, uint64), 16, 0);
      i += 2;

TIP

解析 %p (指针)

    } else if(c0 == 'p'){
      printptr(va_arg(ap, uint64));

TIP

解析 %s (字符串)

    } else if(c0 == 's'){
      if((s = va_arg(ap, char*)) == 0)
        s = "(null)";
      for(; *s; s++)
        consputc(*s);

TIP

解析 %% (打印一个 '%')

    } else if(c0 == '%'){
      consputc('%');
    } else if(c0 == 0){
      break;
    } else {

TIP

遇到未知的格式说明符,如 "%z",则打印 "%z" 以引起开发者注意。

      consputc('%');
      consputc(c0);
    }

#if 0
    switch(c){
    case 'd':
      printint(va_arg(ap, int), 10, 1);
      break;
    case 'x':
      printint(va_arg(ap, int), 16, 1);
      break;
    case 'p':
      printptr(va_arg(ap, uint64));
      break;
    case 's':
      if((s = va_arg(ap, char*)) == 0)
        s = "(null)";
      for(; *s; s++)
        consputc(*s);
      break;
    case '%':
      consputc('%');
      break;
    default:

TIP

Print unknown % sequence to draw attention.

      consputc('%');
      consputc(c);
      break;
    }
#endif
  }
  va_end(ap);

  if(locking)
    release(&pr.lock);

  return 0;
}

TIP

当内核遇到无法恢复的严重错误时调用此函数。 它会打印一条错误消息,然后停止所有 CPU 的执行。

void
panic(char *s)
{
  pr.locking = 0;
  printf("内核恐慌: ");
  printf("%s\n", s);
  panicked = 1;
  for(;;)
    ;
}

TIP

初始化 printf 使用的锁。 在多核环境中,这对于防止并发的 printf 调用输出混乱至关重要。

void
printfinit(void)
{
  initlock(&pr.lock, "pr");
  pr.locking = 1;
}