Linux程序用户态到内核态移植

本文探讨了在Linux内核环境下编程时遇到的限制,如无法使用C库和系统调用,并提供了实现调试消息打印、内存管理、数据类型转换和随机数生成的方法。重点介绍了使用printk函数打印消息、kmalloc和vmalloc内存分配接口、定长数据类型的应用、从文件中获取随机数以及如何在内核态下使用系统调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

          Linux环境下,在内核写程序限制很多,相比用户态程序:不能使用C库、不能使用系统调用、理解内核各个部分的实现原理及相关函数的机制及作用、熟悉内核使用的锁机制并仔细处理跟锁相关的细节

 

之前做过将一个用户态的加密库(包括AESRSA的实现)移植到内核态使用,主要涉及调试消息的打印,内存空间申请与释放,数据类型的转换,随机数的生成等问题。

 

没有printf,如何打印消息?

内核中可用printk函数来向将信息写到标准输出(或者日志文件)上,printk提供多种日志级别以适应多种不同的环境(如“调试消息”、“警告消息”、“紧急消息”)。

 

没有malloc,如何申请/释放内存?

内核栈的空间很小,通常接近两个物理页的大小,因此内核程序不能过度的使用栈空间,容易导致内核栈溢出。内核中提供kmallocvmalloc内存分配接口,kmalloc分配的内存在物理上连续,理论上有128KB大小的限制,其基于伙伴系统实现;vmalloc将一组非连续的物理页映射到连续的逻辑地址上,因此当需要获取大块内存时,需要使用到vmallloc

 

数据类型的转换

内核中定义了一系列的定长数据类型,如__u8, __s8表示八位的无符号和有符号数,__s32, __u32表示32位的无符号和有符号数,使用定长数据类型,可增强程序的移植性。

 

如何获取随机数

不能使用C库的rand()函数了,可从/dev/random/dev/urandom(收集系统的中断信息,这些信息都是随机产生的)文件中获取随机数。要想从这两个虚拟设备中读取数据,首先要解决如何在内核态读取文件的问题,该问题也可以扩展为如何在内核态使用系统调用,参见:http://blog.sina.com.cn/s/blog_6237dcca0100fb3y.html


系统调用的参数要求必须来自用户空间,所以,当在内核中使用系统调用的时候,必须使用set_fs(get_ds())来改变用户空间的限制,即扩大用户空间范围,因此即可使用在内核中的参数了。

 

注:在进程进入内核态后,FS寄存器默认指向进程的数据段。而DS,ES则指向内核数据段。在用户态运行时,这些寄存器都指向用户数据段

例:

static int __initinit(void)
{
        mm_segment_told_fs;
        printk("Hello,I'm the module that intends to write messages tofile.\n");


        if(file == NULL)
                file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);
        if (IS_ERR(file)) {
                printk("erroroccured while opening file %s, exiting...\n", MY_FILE);
                return 0;
        }

        sprintf(buf,"%s", "The Messages.");

        old_fs = get_fs();
        set_fs(KERNEL_DS);
        file->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos);
        set_fs(old_fs);


        return 0;
}
如果报错,很可能是因为使用的缓冲区超过了用户空间的地址范围。一般系统调用会要求你使用的缓冲区不能在内核区。这个可以用set_fs()、get_fs()来解决。在读写文件前先得到当前fs: 
  mm_segment_t   old_fs=get_fs(); 
  并设置当前fs为内核fs:set_fs(KERNEL_DS); 
  在读写文件后再恢复原先fs:  set_fs(old_fs);  
 set_fs()、get_fs()等相关宏在文件include/asm/uaccess.h中定义。 
 个人感觉这个办法比较简单。 

  文件uaccess.h:
  #define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
  #define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF)
  #define USER_DS MAKE_MM_SEG(PAGE_OFFSET)
  #define get_ds() (KERNEL_DS)
  // 取得内核空间范围 YRG
  #define get_fs() (current->addr_limit)
  // 取得用户空间范围 YRG
  #define set_fs(x) (current->addr_limit =(x))
  // 设置用户空间范围 YRG

解释一点:
   
系统调用本来是提供给用户空间的程序访问的,所以,对传递给它的参数(比如上面的buf),它默认会认为来自用户空间,在->write()函数中,为了保护内核空间,一般会用get_fs()得到的值来和USER_DS进行比较,从而防止用户空间程序“蓄意”破坏内核空间;

  而现在要在内核空间使用系统调用,此时传递给->write()的参数地址就是内核空间的地址了,在USER_DS之上(USER_DS~KERNEL_DS),如果不做任何其它处理,在write()函数中,会认为该地址超过了USER_DS范围,所以会认为是用户空间的“蓄意破坏”,从而不允许进一步的执行;为了解决这个问题;set_fs(KERNEL_DS);将其能访问的空间限制扩大到KERNEL_DS,这样就可以在内核顺利使用系统调用了!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值