Printk实现流程

一、初步结论
1.如何把字符串放到缓存,如何从缓存写到串口。
首先是在关中断,关调度,保持logbuf_lock自旋锁的情况下,将数据格式化后,放到printk_buf缓冲区,其大小为1K,也就是说,每次printk只能打印1K的内容。格式化完毕后,将数据再复制到log_buf缓冲区。由于在向串口输出的过程中,会暂时打开自旋锁,所以在SMP下,其他CPU可能继续向log_buf中存放数据,并由驱动输出。简单的说:调用一次printk,需要打印的并不仅仅是本次printk需要输出的内容,还可能有其他CPU上输出的内容。
从缓存中输出到真实的设备是由注册的控制台个数决定的。注册多少个设备,就向多少个设备输出。也就是说,如果注册了两个串口控制台,那么关中断的时间就会增加一倍。
2.采用中断还是轮询。
采用的是轮询方式。

二、代码流程详细分析
Printk的主要流程由vprintk辅助函数实现。
Vprintk的流程如下:

1.关抢占。这是因为需要调用smp_processor_id,如果不关调度,并且是在异常或者内核线程中调用printk的话,可能会导致任务飘移到其他CPU,从而导致后续逻辑判断错误。
2.如果当前上下文是在异常中,那么异常可能是printk引起的,由于后续会使用logbuf_lock自旋锁,为了防止自旋锁嵌套,导致系统死锁,调用zap_locks,复位自旋锁。
3.关中断,并获得logbuf_lock自旋锁,以保护printk_buf缓冲区,这样即使是在SMP上,printk打印出来的内容都不会交错输出。
4.将格式化的信息输出到printk_buf缓冲区,并复制到log_buf。
5.使用down_trylock非阻塞的申请console_sem信号量。视情况进入以下处理流程:
A:如果申请成功,则将log_buf中的内容输出到物理设备上。
B:如果申请失败,则开中断并释放自旋锁,并退出。本次输出到printk_buf缓冲区的内容,由其他printk执行路径输出到物理设备上。所以,某次printk输出到物理设备上的内容,并不一定仅仅是本次传递给printk的内容,还有可能是其他CPU上的printk输出到缓冲区中的内容。

当printk向缓冲区输出申请console_sem信号量成功后,printk会向物理设备输出,在这个过程中,会临时释放自旋锁,以允许其他CPU向缓冲区中输出内容。具体流程如下:
1.暂时释放logbuf_lock自旋锁,这样,其他CPU可以向log_buf缓冲区中输出数据。
2.调用release_console_sem向物理设备输出内容。
3.release_console_sem首先在循环执行如下流程:
A:得到当前log_buf缓冲区的起始地址和物理地址。如果没有新的数据,则退出。新的数据可能是其他cpu调用printk输出到缓冲区中的。
B:打开自旋锁,允许其他CPU向缓冲区中输出数据。
C:调用call_console_drivers将当前缓冲区中的内容输出到物理设备。本次输出的内容,不包含释放自旋锁后,其他CPU向缓冲区中输出的内容。
4. 循环结束后,release_console_sem判断本次printk是否确实输出了数据(当printk参数为空串时,可能没有数据),如果有数据输出,就唤醒klogd进程。

call_console_drivers函数首先将输出内容按行分拆,并将每行数据传递给当前注册的所有控制台设备,调用控制台驱动将数据打印出来。由于整个过程都处于关中断状态,所以,只要某个注册的控制台驱动是慢速设备,都会影响到关中断的时间。更为严重的是:如果某个CPU在调用printk输出期间,其他CPU也准备向控制台输出,那么其他CPU也会因为申请不到logbuf_lock自旋锁而处于忙等状态。

串口驱动函数:
耗时的过程是向串口等慢速设备输出数据的过程。由于printk可以在中断中使用,所以也决定了驱动不能采用中断方式,而必须是轮询串口的方式。

8250串口驱动回调函数serial8250_console_write实现了控制台write回调。这个函数的流程如下:
1.首先调用一下NMI软件狗回调函数。看来串口驱动已经意识到慢速设备对系统的影响了。但是这个回调没有办法解决硬件狗的问题。
2.关中断。
3.对需要输出的每个字符,回调serial8250_console_putchar函数。
4.最费时的地方就在serial8250_console_putchar中。它调用wait_for_xmitr(up, UART_LSR_THRE);一句,轮询串口状态。直到串口可写,才向串口输出一个字符。
5.恢复中断状态。实际上,在printk上下文中,中断此时仍然是关闭的,直到log_buf缓冲区中的所有数据都输出到串口。

### 回答1: 北航os lab1中的printk是一个用于内核调试的函数。printk函数是Linux内核中一个非常重要的调试输出函数,用于在内核中输出调试信息。 printk函数的原型为: int printk(const char *fmt, ...); 该函数可以接受一个或多个参数,类似于C语言中的printf函数。第一个参数是一个格式化字符串,后面的参数根据格式化字符串中的占位符来确定输出的内容。 printk函数通过将信息打印到内核缓冲区中,然后再将缓冲区的内容发送到标准输出或日志文件中。这样可以避免在内核中直接使用标准输出函数,因为标准输出函数通常会产生内核安全问题。 在lab1中,我们通过使用printk函数来输出一些调试信息,以便我们在运行内核时能够观察到一些重要的状态信息。这些信息对于调试内核错误和理解内核运行过程非常有帮助。 在实际使用中,我们可以在代码中的关键位置调用printk函数输出调试信息,比如在函数调用的入口处、循环的每一次迭代中等。输出的信息可以是变量的值、函数的返回结果、状态标志等。通过观察这些输出信息,我们可以更好地理解内核的运行过程,并找出潜在的问题和改进的空间。 总之,北航os lab1中的printk函数是一个用于内核调试的重要函数,在调试内核错误和理解内核运行过程中发挥着非常重要的作用。 ### 回答2: 北航OS lab1中的printk是一个用于打印输出信息的函数。在操作系统编程中,打印输出信息对于调试和排错是非常重要的。printk函数可以将我们想要输出的信息打印到控制台或者文件中。该函数可以接受不同类型的参数,包括字符串、整数和指针等。 在lab1中,我们需要实现一个简化版的printk函数。通过实现这个函数,我们可以加深对操作系统内核的理解,学习和掌握操作系统中的内核级调试技术。 在实现printk函数时,需要考虑几个关键点。首先是参数的处理,我们需要根据参数的不同类型来确定打印输出的格式。其次是打印输出的位置,可以选择将打印的信息输出到控制台上,或者写入到一个文件中。最后是打印输出的性能优化,可以通过缓冲区和格式化输出等优化技术来提高打印输出的效率。 通过实现printk函数,我们可以在内核中方便地输出调试信息,帮助我们追踪和分析代码的执行流程,进而更好地理解操作系统的运行机制。同时,printk函数在操作系统开发中也是一个基础的工具函数,熟练掌握它对于后续的实验和项目开发都有很大的帮助。 总之,北航OS lab1中的printk是一个重要的函数,它可以帮助我们实现内核级调试和输出相关信息,对于操作系统的学习和开发都具有重要意义。 ### 回答3: 北航OS Lab1是指北京航空航天大学操作系统实验中的第一个实验,即实现一个简化版的printk函数。 printk函数是操作系统中用于将信息打印到屏幕或日志文件的函数,可以帮助调试程序或输出程序的运行状态。在北航OS Lab1的实验中,我们需要实现一个类似的函数。 具体实现的过程涉及以下步骤: 1. 实现字符串输出功能:我们需要编写代码来输出字符串,将字符串的各个字符逐一输出到屏幕或日志文件。 2. 实现格式化输出功能:在实际开发中,我们通常希望能够输出变量的值,而不仅仅是字符串。因此,我们需要实现格式化输出的功能,即能够根据不同的格式输出不同类型的变量。 3. 添加参数支持:为了使printk函数更加灵活,我们还需要实现可变参数的支持,即能够接收不确定数量的参数。 4. 添加调试信息:为了方便调试程序,我们还需要在输出的内容中添加相关的调试信息,比如输出所在的文件和行号。 在完成上述步骤后,我们就能够实现一个简化版的printk函数。它能够输出字符串、格式化输出不同类型的变量、接收可变数量的参数,并且在输出中添加调试信息。 总之,北航OS Lab1中的打印函数(printk)是一个基于字符串输出的简化版,通过实现字符串输出、格式化输出、参数支持和调试信息,我们能够实现一个功能相对完善的打印函数,用于帮助调试和输出程序运行状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值