函数会把内容输出到ring 中,这个环形缓冲区的设计可以高效地存储和管理内核打印的日志信息。当ring 满了之后,新的日志会覆盖最早的日志,这种设计可以保证内核日志的实时性。

在用户态,我们可以通过多种方式查看内核日志,比如使用dmesg命令、读取/proc/kmsg、/dev/kmsg文件或者使用函数等。

printk不加换行_printk和printf_printk

添加图片注释,不超过 140 字(可选)

要想启动记录需要打开内核选项和

/* record buffer */
#define LOG_ALIGN __alignof__(unsigned long)
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
#define LOG_BUF_LEN_MAX (u32)(1 << 31)
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
static char *log_buf = __log_buf;
static u32 log_buf_len = __LOG_BUF_LEN;

Linux 内核共提供了八种不同的消息级别,分为级别 0~7。数值越大,表示级别越低

#define KERN_SOH	"01"		/* ASCII Start Of Header */
#define KERN_EMERG	KERN_SOH "0"	/* system is unusable */
#define KERN_ALERT	KERN_SOH "1"	/* action must be taken immediately */
#define KERN_CRIT	KERN_SOH "2"	/* critical conditions */
#define KERN_ERR	KERN_SOH "3"	/* error conditions */
#define KERN_WARNING	KERN_SOH "4"	/* warning conditions */
#define KERN_NOTICE	KERN_SOH "5"	/* normal but significant condition */
#define KERN_INFO	KERN_SOH "6"	/* informational */
#define KERN_DEBUG	KERN_SOH "7"	/* debug-level messages */
#define KERN_DEFAULT	""		/* the default kernel loglevel */

修改系统内核日志打印权限/proc/sys//

查看命令"cat /proc/sys/kernel/printk"
修改命令"echo 8 > /proc/sys/kernel/printk"
int console_printk[4] = {
	CONSOLE_LOGLEVEL_DEFAULT,	/* console_loglevel */
	MESSAGE_LOGLEVEL_DEFAULT,	/* default_message_loglevel */
	CONSOLE_LOGLEVEL_MIN,		/* minimum_console_loglevel */
	CONSOLE_LOGLEVEL_DEFAULT,	/* default_console_loglevel */
};
EXPORT_SYMBOL_GPL(console_printk);

printk和printf_printk_printk不加换行

添加图片注释,不超过 140 字(可选)

第一个值 4:代表当前控制台日志级别(),即控制台输出的日志级别。 第二个值 4:代表默认控制台日志级别(evel),即默认情况下控制台输出的日志级别。 第三个值 1:代表最小控制台日志级别(evel),即允许输出到控制台的最低日志级别。 第四个值 7:代表当前内核日志级别(evel),即内核当前的日志级别。

dmesg&/dev/kmsg实战

dmesg命令在默认情况下会读取/dev/kmsg文件来获取内核日志,代码实现了和两个接口,通过调用open函数打开文件并使用read函数逐条读取日志信息

const struct file_operations kmsg_fops = {
	.open = devkmsg_open,
	.read = devkmsg_read,
	.write_iter = devkmsg_write,
	.llseek = devkmsg_llseek,
	.poll = devkmsg_poll,
	.release = devkmsg_release,
};

printk和printf_printk不加换行_printk

添加图片注释,不超过 140 字(可选)

printk_printk和printf_printk不加换行

添加图片注释,不超过 140 字(可选)

printk不加换行_printk和printf_printk

添加图片注释,不超过 140 字(可选)

下面讲一下最原始内核0.11版本的代码实现,理解了最原始的实现,对后面的改动就很简单了函数调用关系

->->

格式化输出

的函数,用于在控制台上输出格式化的字符串。 该函数使用了可变参数列表,可以接受任意数量的参数。 首先,使用宏初始化一个类型的变量args,然后使用函数将格式化的字符串和可变参数列表args打印到一个缓冲区buf中,返回打印的字符数。 首先将fs寄存器和ds寄存器的值压入栈中,然后将fs寄存器的值设置为ds寄存器的值,这是为了在访问buf时使用fs寄存器,因为fs寄存器通常用于指向当前进程的TSS(任务状态段),而TSS中包含了当前进程的内核栈。然后将打印的字符数i压入栈中,接着将buf的地址和一个值为0的参数依次压入栈中,调用函数将buf中的内容写入控制台。最后,将栈中的值弹出,恢复fs寄存器和ds寄存器的值,返回打印的字符数i。

函数源码

static char buf[1024];
extern int vsprintf(char * buf, const char * fmt, va_list args);
int printk(const char *fmt, ...)
{
	va_list args;
	int i;
	va_start(args, fmt);
	i=vsprintf(buf,fmt,args);
	va_end(args);
	__asm__("push %%fsnt"
		"push %%dsnt"
		"pop %%fsnt"
		"pushl %0nt"
		"pushl $bufnt"
		"pushl $0nt"
		"call tty_writent"
		"addl $8,%%espnt"
		"popl %0nt"
		"pop %%fs"
		::"r" (i):"ax","cx","dx");
	return i;
}

终端输出函数

函数,用于将字符数组buf中的内容写入到指定通道对应的终端设备中。函数的返回值是成功写入的字符数。 函数首先检查通道号是否大于2或者写入字符数nr是否小于0,如果是,则返回-1表示错误。 接下来,函数通过将通道号与相加,得到对应的结构体指针tty。 然后,函数进入一个循环,直到写入字符数nr为0。在每次循环中,函数会调用函数,如果写入队列tty->已满,则会使当前进程进入睡眠状态,直到队列有足够的空间。 接着,函数检查当前进程是否有信号待处理,如果有,则跳出循环。 然后,函数进入一个嵌套循环,直到写入字符数nr为0或者写入队列tty->已满。在每次循环中,函数从buf中读取一个字符c,并根据终端设备的配置进行处理。处理包括:将回车符'r'转换为换行符'n'(如果配置允许),将换行符'n'转换为回车符'r'(如果配置允许),将换行符'n'转换为回车符'r'并插入队列中(如果配置允许),将字符转换为大写(如果配置允许)。然后,指针b向后移动一位,写入字符数nr减1,重置为0,并将字符c插入到写入队列tty->中。 接着,函数调用tty->write函数,将写入队列中的字符进行实际的写入操作。 如果写入字符数nr仍大于0,则调用函数进行进程调度,让其他进程有机会执行。

int tty_write(unsigned channel, char * buf, int nr)
{
	static int cr_flag=0;
	struct tty_struct * tty;
	char c, *b=buf;
	if (channel>2 || nr0) {
		sleep_if_full(&tty->write_q);
		if (current->signal)
			break;
		while (nr>0 && !FULL(tty->write_q)) {
			c=get_fs_byte(b);
			if (O_POST(tty)) {
				if (c=='r' && O_CRNL(tty))
					c='n';
				else if (c=='n' && O_NLRET(tty))
					c='r';
				if (c=='n' && !cr_flag && O_NLCR(tty)) {
					cr_flag = 1;
					PUTCH(13,tty->write_q);
					continue;
				}
				if (O_LCUC(tty))
					c=toupper(c);
			}
			b++; nr--;
			cr_flag = 0;
			PUTCH(c,tty->write_q);
		}
		tty->write(tty);
		if (nr>0)
			schedule();
	}
	return (b-buf);
}

将字符写入终端

函数内部定义了一些变量,包括nr和c,分别表示写入队列中字符的数量和当前字符。 接下来使用while循环,循环次数为写入队列中字符的数量。 在循环中,使用GETCH宏从写入队列中获取一个字符,并使用语句根据字符的不同进行不同的操作。 在case 0中,如果字符的ASCII码在31和127之间,表示是可打印字符,会进行一系列的操作,包括判断是否需要换行、设置字符属性、更新光标位置等。 在case 1中,如果字符是'[',表示后面是控制序列,会进入case 2进行处理。 在case 2中,会解析控制序列中的参数,并根据参数执行相应的操作,比如移动光标、清除屏幕等。 最后,调用函数设置光标位置。

void con_write(struct tty_struct * tty)
{
	int nr;
	char c;
	nr = CHARS(tty->write_q);
	while (nr--) {
		GETCH(tty->write_q,c);
		switch(state) {
			case 0:
				if (c>31 && c=video_num_columns) {
						x -= video_num_columns;
						pos -= video_size_row;
						lf();
					}
					__asm__("movb attr,%%ahnt"
						"movw %%ax,%1nt"
						::"a" (c),"m" (*(short *)pos)
						);
					pos += 2;
					x++;
				} else if (c==27)
					state=1;
				else if (c==10 || c==11 || c==12)
					lf();
				else if (c==13)
					cr();
				else if (c==ERASE_CHAR(tty))
					del();
				else if (c==8) {
					if (x) {
						x--;
						pos -= 2;
					}
				} else if (c==9) {
					c=8-(x&7);
					x += c;
					pos += c<video_num_columns) {
						x -= video_num_columns;
						pos -= video_size_row;
						lf();
					}
					c=9;
				} else if (c==7)
					sysbeep();
				break;
			case 1:
				state=0;
				if (c=='[')
					state=2;
				else if (c=='E')
					gotoxy(0,y+1);
				else if (c=='M')
					ri();
				else if (c=='D')
					lf();
				else if (c=='Z')
					respond(tty);
				else if (x=='7')
					save_cur();
				else if (x=='8')
					restore_cur();
				break;
			case 2:
				for(npar=0;npar<NPAR;npar++)
					par[npar]=0;
				npar=0;
				state=3;
				if ((ques=(c=='?')))
					break;
			case 3:
				if (c==';' && npar='0' && c<='9') {
					par[npar]=10*par[npar]+c-'0';
					break;
				} else state=4;
			case 4:
				state=0;
				switch(c) {
					case 'G': case '`':
						if (par[0]) par[0]--;
						gotoxy(par[0],y);
						break;
					case 'A':
						if (!par[0]) par[0]++;
						gotoxy(x,y-par[0]);
						break;
					case 'B': case 'e':
						if (!par[0]) par[0]++;
						gotoxy(x,y+par[0]);
						break;
					case 'C': case 'a':
						if (!par[0]) par[0]++;
						gotoxy(x+par[0],y);
						break;
					case 'D':
						if (!par[0]) par[0]++;
						gotoxy(x-par[0],y);
						break;
					case 'E':
						if (!par[0]) par[0]++;
						gotoxy(0,y+par[0]);
						break;
					case 'F':
						if (!par[0]) par[0]++;
						gotoxy(0,y-par[0]);
						break;
					case 'd':
						if (par[0]) par[0]--;
						gotoxy(x,par[0]);
						break;
					case 'H': case 'f':
						if (par[0]) par[0]--;
						if (par[1]) par[1]--;
						gotoxy(par[1],par[0]);
						break;
					case 'J':
						csi_J(par[0]);
						break;
					case 'K':
						csi_K(par[0]);
						break;
					case 'L':
						csi_L(par[0]);
						break;
					case 'M':
						csi_M(par[0]);
						break;
					case 'P':
						csi_P(par[0]);
						break;
					case '@':
						csi_at(par[0]);
						break;
					case 'm':
						csi_m();
						break;
					case 'r':
						if (par[0]) par[0]--;
						if (!par[1]) par[1] = video_num_lines;
						if (par[0] < par[1] &&
						    par[1] <= video_num_lines) {
							top=par[0];
							bottom=par[1];
						}
						break;
					case 's':
						save_cur();
						break;
					case 'u':
						restore_cur();
						break;
				}
		}
	}
	set_cursor();
}

———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666

声明:1、本内容转载于网络,版权归原作者所有!2、本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。3、本内容若侵犯到你的版权利益,请联系我们,会尽快给予删除处理!