xv6 源码阅读 · 第三章

第三章·printf.c

在 xv6 操作系统中,printf.c 文件包含了实现 printf 函数的代码。printf 是一个输出函数,用于将文本格式化后输出到控制台或屏幕。xv6 中的 printf 实现较为简单,不像标准 C 库中的 printf 那么复杂,它通过系统调用与内核的 I/O 子系统进行交互。因为printf()于其他代码文件相对独立,并且在其他源文件中会反复调用,故而单独拿出一个章节分析理解,可以跳过,不影响其他代码的理解。

void printfinit(void)

\kernel\printf.c
void printfinit(void)
{
  initlock(&pr.lock, "pr");  // 初始化打印锁
  pr.locking = 1;             // 设置锁的标志为 1
}

printfinit 函数的作用是初始化 printf 输出时需要的锁,以确保并发环境下多个打印任务之间不会相互干扰。pr.locking 则是用来控制锁的状态,可能用于 printf 函数在实际打印时进行判断。

由于锁的使用存在于大部分源码中,并且是一个相对完整的体系,对于锁的实现方法和相关代码,在下一章会详细分析。在此之前,对于锁,只需要知道其在代码中的具体作用。

void panic(char *s)

void panic(char *s)
{
  pr.locking = 0;              // 禁用锁定
  printf("panic: ");           // 输出前缀 "panic: "
  printf(s);                   // 输出传入的错误信息 s
  printf("\n");                // 输出换行符
  panicked = 1;                // 设置 panicked 标志为 1,表示发生了 panic
  for(;;)                      // 死循环,阻止程序继续执行
    ;
}

对于 for(;;) 无限循环,计算机会进入死循环,持续消耗计算资源,无法继续执行其他程序。通常情况下,这是一种在发生严重错误时使用的机制,用来阻止程序继续执行,并等待外部干预(如系统重启或通过调试中断退出)。多核CPU中,只有执行panic命令的核心才会终止运行,其他核心不受影响。

void printf(char *fmt, ...) (核心函数)

void printf(char *fmt, ...)
{
  va_list ap;
  int i, c, locking;
  char *s;

  locking = pr.locking;               // 保存当前打印锁定状态
  if(locking)
    acquire(&pr.lock);                // 如果需要锁定,则获取锁

  if (fmt == 0)
    panic("null fmt");                 // 如果格式化字符串为空,触发 panic

  va_start(ap, fmt);                   // 初始化 va_list 类型的变量 ap,用于访问可变参数
  for(i = 0; (c = fmt[i] & 0xff) != 0; i++){  // 遍历格式化字符串 
    if(c != '%'){                      // 如果字符不是格式标识符 %
      consputc(c);                      // 直接输出字符
      continue;
    }									// -> 从这里往下就是实现类似于printf("%d",n)这种操作
    c = fmt[++i] & 0xff;              // 获取格式标识符后的字符
    if(c == 0)                         // 如果格式标识符后面没有字符,跳出循环
      break;
    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) // 如果字符串为空,打印 "(null)"
        s = "(null)";
      for(; *s; s++)                    // 输出字符串的每个字符
        consputc(*s);
      break;
    case '%':                          // 打印百分号字符 '%'
      consputc('%');
      break;
    default:                           // 打印不支持的格式标识符
      consputc('%');
      consputc(c);
      break;
    }
  }

  if(locking)
    release(&pr.lock);                // 如果需要锁定,则释放锁
}

va_list 是 C 语言中的一个类型,用于处理函数的可变参数。它是 <stdarg.h> 头文件中定义的,用来访问传递给函数的参数。va_list 是一个指向函数参数列表的指针,配合 va_startva_argva_end 等宏一起使用,能够在函数内部访问不定数量和类型的参数。

va_start(ap, fmt);:扩展为:__builtin_va_start(ap,fmt)

__builtin_va_start(ap, fmt) 是 GCC 提供的一个内建函数,通常与 va_list 一起使用。它是 va_start 宏的一个底层实现,通常用于初始化 va_list 类型的变量,以便访问可变参数列表。(就是访问参数用到的,再深就不要看了,对于xv6系统的理解到这里就可)下面介绍下该函数中用到的另外两个内置函数:

static void printptr(uint64 x)

static void printptr(uint64 x)
{
  int i;
  consputc('0');  // 输出 '0'
  consputc('x');  // 输出 'x'
  
  for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4)
    consputc(digits[x >> (sizeof(uint64) * 8 - 4)]);  // 输出十六进制数字
}

digits[] 数组是一个包含十六进制字符的数组:

static char digits[] = "0123456789abcdef";

这样,通过 digits[x >> 60] 就能得到对应的十六进制字符。

static void printint(int xx, int base, int sign)

static void printint(int xx, int base, int sign)
{
  char buf[16];          // 存储转换后的字符数组
  int i;
  uint x;

  // 处理负数情况
  if(sign && (sign = xx < 0))
    x = -xx;             // 如果是负数,取绝对值
  else
    x = xx;              // 否则,直接赋值

  i = 0;
  do {
    buf[i++] = digits[x % base]; // 取余数,获得当前数字的字符表示
  } while((x /= base) != 0);     // 除以基数,得到下一位的数字

  if(sign)
    buf[i++] = '-';     // 如果是负数,添加负号

  while(--i >= 0)
    consputc(buf[i]);    // 从字符数组末尾开始输出,逆序打印
}

printint 函数,用于将一个整数以指定的基数(如十进制、十六进制等)格式化输出。它支持有符号整数的处理,并且通过 consputc 函数将每个字符输出到控制台。

对于上面的代码,可能会看得比较蒙,翻来覆去的不知道在干啥呢。这是因为对于console控制台的输出,每次只会输出一个”单字节字符“,比如说输出123,那么简化后的输出如下:

consputc('1');
consputc('2');
consputc('3');

也就是先输出1,再输出2,最后再输出3。所以先用反复的计算得到1,2,3,再输出,理解了这个过程,再看上面的代码,就很清晰了。这里我们要学会一个思想,看这种底层源码时,不要把常用的类似于printf()这种函数的实现想的那么理所当然,时刻记住我们正在和傻傻的硬件打交道 (╬▔皿▔)╯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值