您的位置:首页 > 运维架构 > Linux

Linux内核态和用户态的区别

2015-01-23 17:54 85 查看
当一个任务(进程)执行系统调用而陷入内核代码中执 行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中 执行。

当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

当进程在执行用户自己的代码时,则称其处于用户运行态(用 户态)。即此时处理器在特权级最低的(3级)用户代码中运行。

当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核 态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系,intel cpu提供Ring0-Ring3三种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为 内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。

Linux进程的4GB地址空间,3G-4G部 分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。

用户运行一个程序,该程序所创建的进程开始是运 行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必 须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能
随意操作内核地址空间,具有一定的安全保护作用。

至于说保护模式,是说通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程的地址空间中的数据。

在内核态下,CPU可执行任何指令,在用户态下CPU只能执行非特权指令。当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态,只能通过中断 的方式进入内核态。

一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态.

内核态和用户态中sscanf的区别

需求:将一个16进制的字符转转换成16进制的数值存储起来。譬如:"ABCD1234"转换之后,用一个字符数组存储,其值依次为0xAB,0xCD,0x12,0x34.

实现方法:考虑使用sscanf进行格式输入到buf中,将每两个16进制字符转换成一个char型的数字。

环境: kerne 2.6.18.3, gcc 4.1.2

本文欢迎自由转载,但请标明出处和本文链接,并保持本文的完整性。

CU: Godbach

Blog:http://blog.chinaunix.net/u/33048/index.html

Dec 30, 2009

现在用户空间测试一下,代码usscanf.c:

/*

*Test sscanf func in userspace

*/

#include <stdio.h>

#include <string.h>

#define HEXSTR_LEN 16

int main(void)

{

char hexstr[HEXSTR_LEN+1] = "1234567890ABCDEF";

unsigned char hex[HEXSTR_LEN/2] = {0x00};

int tmp = 0;

int i = 0;

printf("hex str: %s\n", hexstr);

printf("hex val: ");

for(i = 0; i < strlen(hexstr)/2; i++)

{

sscanf(hexstr + 2 * i, "%2x", &tmp);

hex[i] = (unsigned char)tmp;

printf("%.2x ", hex[i]);

tmp = 0;

}

printf("\n");

return 0;

}

测试结果如下:

[root@localhost ksscanf]# ./usscanf

hex str: 1234567890ABCDEF

hex val: 12 34 56 78 90 ab cd ef

这样的结果如预期,正确的将16进制字符串进了转换。

那么在内核态呢,内核态也有封装的sscanf供调用。那么我们同时在内核态下测试,代码ksscanf.c如下:

/*

*Test sscanf func in kernel

*/

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/string.h>

#define HEXSTR_LEN 16

static int __init init(void)

{

char hexstr[HEXSTR_LEN+1] = "1234567890ABCDEF";

unsigned char hex[HEXSTR_LEN/2] = {0x00};

int tmp = 0;

int i = 0;

printk("sscanf test module init...\n");

printk("hex str: %s\n", hexstr);

printk("hex val: ");

for(i = 0; i < strlen(hexstr)/2; i++)

{

sscanf(hexstr + 2 * i, "%2x", &tmp);

hex[i] = (unsigned char)tmp;

printk("%.2x ", hex[i]);

tmp = 0;

}

printk("\n");

return 0;

}

static void __exit fini(void)

{

printk("sscanf test module exit...\n");

return ;

}

module_init(init);

module_exit(fini);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Godbach");

MODULE_DESCRIPTION("This is a LKM for testing sscanf.");

测试该模块的代码,执行结果如下:

Dec 30 15:03:49 localhost kernel: sscanf test module init...

Dec 30 15:03:49 localhost kernel: hex str: 1234567890ABCDEF

Dec 30 15:03:49 localhost kernel: hex val: ef ef ef ef ef ef ef ef

发现内核态转换的16进制数值全是ef,很明显是有问题的。经过多次测试并转换过程中打印tmp的数值发现,内核转换时一直用的是hexstr的最后八个字符,即“90ABCDEF”,然后用unsigned char进行截断,所以结果就是ef,hex的8个字节都是ef。

看来用户态和内核态使用sscanf时对已有字符串读取的方向不一样,用户态直接从hexstr的开头取自取字符,而内核态则取字符串最后有效的若干个16进制字符。

因此,有这样应用的时候,一定要注意这样的问题。

如何避免或者按照我们的意愿执行呢?

那就每次将hexstr中取2个字符到一个新的缓存hex_ch[2]中,然后对hex_ch执行sscanf,这样每次仅仅转换两个字符,就没有上述的问题了。

这里不再显示用户态修改后的执行结果,而仅贴出内核态修改后的代码和执行结果:

代码如下:

/*

*Test sscanf func in kernel

*/

#include <linux/version.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/string.h>

#define HEXSTR_LEN 16

static int __init init(void)

{

char hexstr[HEXSTR_LEN+1] = "1234567890ABCDEF";

unsigned char hex[HEXSTR_LEN/2] = {0x00};

int tmp = 0;

int i = 0;

unsigned char hex_ch[2] = {0x00};

printk("sscanf test module init...\n");

printk("hex str: %s\n", hexstr);

printk("hex val: ");

for(i = 0; i < strlen(hexstr)/2; i++)

{

memset(hex_ch, 0x00, 2);

memcpy(hex_ch, hexstr + 2 * i, 2);

sscanf(hex_ch, "%2x", &tmp);

hex = (unsigned char)tmp;

printk("%.2x ", hex);

tmp = 0;

}

printk("\n");

return 0;

}

static void __exit fini(void)

{

printk("sscanf test module exit...\n");

return ;

}

module_init(init);

module_exit(fini);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Godbach");

MODULE_DESCRIPTION("This is a LKM for testing sscanf.");

执行结果如下:

Dec 30 15:30:25 localhost kernel: sscanf test module init...

Dec 30 15:30:25 localhost kernel: hex str: 1234567890ABCDEF

Dec 30 15:30:25 localhost kernel: hex val: 12 34 56 78 90 ab cd ef
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: