Linux内核模块编程问题

最近在做的实验,以内核模块方式实现,现在就内核模块编程遇到的的一些问题,做以记录如下:

1.内核函数访问用户态空间

在内核模块中若想要打开或者写一个文件时,可以调用内核中的open/write函数,但是内核中的这些函数其中的一些参数是需要从用户态传过来的,也就是有“__user”修饰的函数(该类函数需要进行内存地址的检查变换)。如果在内核中直接指定一个文件或者缓冲区(内核空间中的),则会报错“Bad address”(返回-EFAULT),因为现在这些函数需要的是从用户空间地址,这些内核函数会对参数进行检查,判断其地址是不是用户空间的。 
为了解决用户空间和内核空间数据交换的问题,有两种方式可以解决:

1.使用brk内核函数

用户态可以使用malloc函数申请内存,其在内核中是通过brk内核函数实现的,可以改变数据段的大小,为了实现用户态和内核态数据的交换,就可以依靠brk来实现。在内核模块中通过current可以定位到当前进程数据段大小,然后通过内核函数brk增加当前进程的内存空间,最后就可以通过copy_to_user/copy_from_user进行最终数据的交换。比如在内核中使用open打开文件,可以如下进行:

unsigned long getUserMem(int length)
{
unsigned long mmm = 0;
int ret;
 
mmm = current->mm->brk; //获取当前进程数据段的大小
ret = brk(mmm+length); //利用brk为当前进程增加length大小的内存空间
if(ret <0)
{
printk(KERN_ERR "Can't allocate userspace mem\n");
return (-ENOMEM);
}
return mmm;
}
 
char kernelBuf[256] = "test"; //内核模块中指定要打开的文件名
int len = strlen(kernelBuf);
//将filename指向getUserMem中返回的地址,此时filename指向的就是用户空间的缓冲区
char *filename = (void*)getUserMem(len) + 2;
 
copy_to_user(filename, kernelBuf, len); //将内核缓冲区中的kernelBuf通过copy_to_user复制到刚申请的用户缓冲区中
open(filename, O_RDWR, 0); //调用内核函数open打开,若是第一个参数使用kernelBuf则报错“Bad address”

方法二:使用set_fs和get_fs

使用set_fs()函数或宏(set_fs()可能是宏定义),如果为函数,其原形如下: 
void set_fs(mm_segment_t fs); 
  该函数的作用是改变kernel对内存地址检查的处理方式,其实该函数的参数fs只有两个取值:USER_DS,KERNEL_DS,分别代表用户空间和内核空间,默认情况下,kernel取值为USER_DS,即对用户空间地址检查并做变换。那么要在这种对内存地址做检查变换的函数中使用内核空间地址,就需要使用set_fs(KERNEL_DS)进行设置。get_fs()一般也可能是宏定义,它的作用是取得当前的设置,这两个函数的一般用法为:

mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS); //或者set_fs(get_ds());
...... //与内存有关的操作 ,open函数传内核空间的地址
set_fs(old_fs);

还有一些其它的内核函数也有用__user修饰的参数,在kernel中需要用kernel空间的内存代替时,都可以使用类似办法。 
会根据get_fs()的值决定是否执行函数参数有效性检查,若get_fs() ==USER_DS则执行检查,等于KERNEL_DS则跳过检查。 
相关宏定义如下:

/*
* The fs value determines whether argument validity checking should be
* performed or not. If get_fs() == USER_DS, checking is performed, with
* get_fs() == KERNEL_DS, checking is bypassed.
*
* For historical reasons, these macros are grossly misnamed.
*/
 
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
 
#define KERNEL_DS MAKE_MM_SEG(-1UL)
#define USER_DS MAKE_MM_SEG(TASK_SIZE_MAX)
 
#define get_ds() (KERNEL_DS)
#define get_fs() (current_thread_info()->addr_limit)
#define set_fs(x) (current_thread_info()->addr_limit = (x))

2.内核模块中使用未导出的函数

一般我们在编写内核模块时,可以直接使用内核中使用EXPORT_SYMBOL或者EXPORT_SYMBOL_GPL导出的函数,没有导出的内核函数不能直接使用。否则会报错未定义:

WARNING: "do_sys_open" [/home/tiany/paper/mod/mySdelNotEcrypt_success/hello.ko] undefined!

那么我们到底能不能使用内核中没有导出的函数呢?答案肯定是可以的,那就是内核符号表。在2.6内核中,为了更好地调试内核,引入了kallsyms。kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成了一个数据块,作为只读数据链接进kernel image。使用root权限可以/proc/kallsyms查看。 
使用kallsyms_lookup_name()(在kernel/kallsyms.c文件中定义的)函数可以找到对应符号在内核中的虚拟地址,要使用它必须启用CONFIG_KALLSYMS编译内核。 包含在头文件linux/kallsyms.h中. kallsyms_lookup_name()接受一个字符串格式内核函数名,返回那个内核函数的地址。定义如下:

/* Lookup the address for this symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name)
{
char namebuf[KSYM_NAME_LEN];
unsigned long i;
unsigned int off;
 
for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));
 
if (strcmp(namebuf, name) == 0)
return kallsyms_addresses[i];
}
return module_kallsyms_lookup_name(name);
}
EXPORT_SYMBOL_GPL(kallsyms_lookup_name);

可以看到该函数已经使用EXPORT_SYMBOL_GPL,可以直接在内核模块中使用。例如,do_sys_open函数原型为long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) 
为了在内核模块中使用它,可以采用如下方法:

//定义钩子函数,返回值和参数都需与do_sys_open函数原型一致
long (*orig_do_sys_open)(int dfd, const char __user *filename, int flags, umode_t mode);
//使用kallsyms_lookup_name函数获取符号do_sys_open的地址
orig_do_sys_open = (void*)kallsyms_lookup_name("do_sys_open");

如上处理后,在后边就可以直接使用orig_do_sys_open(…)方式使用do_sys_open函数了。

其他问题

1.在编译内核模块时有如下警告信息:

warning: the frame size of 4248 bytes is larger than 1024 bytes [-Wframe-larger-than=]

主要是因为内核中设置了堆栈报警大小,其默认值为1024.

  1. 编译内核模块时报错
error: function declaration isnt a prototype [-Werror=strict-prototypes]

主要问题是因为定义的函数没有任何参数,需要添加void解决。

发布了45 篇原创文章 · 获赞 17 · 访问量 7万+
展开阅读全文

Linux内核编程实验三。。。。。

04-30

问题 A 使用ITIMER_REAL型定时器实现一个gettimeofday(),将它设置为每秒产生一个信号, 并计算已经经过的秒数。 问题 B 使用以上实现的 gettimeofday()实现一个精确到微秒级的“闹钟”。 问题 C 实现以上一个父进程和两个子进程并发递归计算不同项数的fibonacci序列的程序, 分析每个进程三种类型定时器测出的时间关系。 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <time.h> //for time() #include <sys/time.h> //for itimeval #include <sys/types.h> //for pid_t #include <sys/wait.h> //for waitpid void psig_real(int signum); //父进程3个信号处理函数 void psig_virtual(int signum); void psig_prof(int signum); void c1sig_real(int signum); //子进程1的3个信号处理 void c1sig_virtual(int signum); void c1sig_prof(int signum); void c2sig_real(int signum); //子进程2的3个信号处理 void c2sig_virtual(int signum); void c2sig_prof(int signum); long fibonacci(unsigned int n); //求斐波那契 //记时变量 long p_real_secs=0,c1_real_secs=0,c2_real_secs=0; long p_virtual_secs=0,c1_virtual_secs=0,c2_virtual_secs=0; long p_prof_secs= 0,c1_prof_secs=0, c2_prof_secs=0; //计时器结构体 struct itimerval p_realt, c1_realt, c2_realt; struct itimerval p_virtt, c1_virtt, c2_virtt; struct itimerval p_proft, c1_proft, c2_proft; //part A的信号处理 void psig_real_gettime(int signum) { p_real_secs += 1; } //实现具有gettimeofday功能 ,接收一个函数 void mygettimeofday(unsigned int i,long (*timefunction)(unsigned int)) { signal(SIGALRM,psig_real_gettime); p_realt.it_value.tv_sec = 0; //0 sec p_realt.it_value.tv_usec = 999999; //999999 usec p_realt.it_interval.tv_sec = 0; p_realt.it_interval.tv_usec = 999999; setitimer(ITIMER_REAL,&p_realt,NULL); timefunction(i); getitimer(ITIMER_REAL,&p_realt); printf("cost time %ld Sec : %ld Msec\n", p_real_secs, (999999 - p_realt.it_value.tv_usec) / 1000); } int wakeupme = 0; //part B闹钟 void dealalarm(int signum) //闹钟处理函数 { wakeupme = 1; } int main(int argc,char **argv) { long fib = 0, microsecond; pid_t pid1, pid2; int msecond, usecond; struct timeval tv; if(argv[1][0] == '-' && argv[1][1] == 'a' && argc == 3) { printf("-----PART A-----\n"); mygettimeofday(atoi(argv[2]),fibonacci); return 0; } else if(argv[1][0] == '-' && argv[1][1] == 'b') { printf("-----PART B-----\n"); struct timeval tv; gettimeofday(&tv, NULL); struct tm *tmptime; tmptime = localtime(&tv.tv_sec); printf("now time > %d-%d-%d %d:%d:%d:%d:%d\n",tmptime->tm_year+1900,tmptime->tm_mon+1,tmptime->tm_mday,tmptime->tm_hour,tmptime->tm_min,tmptime->tm_sec,(int)tv.tv_usec/1000,(int)tv.tv_usec%1000); struct tm inputtime; int year, month, day, hour, minute, second; printf("set alarm : "); scanf("%d-%d-%d %d:%d:%d:%d:%d",&year,&month,&day,&hour, &minute,&second,&msecond,&usecond); inputtime.tm_year = year - 1900; inputtime.tm_mon = month - 1; inputtime.tm_mday = day; inputtime.tm_hour = hour; inputtime.tm_min = minute; inputtime.tm_sec = second; inputtime.tm_isdst = 0; inputtime.tm_wday = 0; inputtime.tm_yday = 0; microsecond = mktime(&inputtime) * 1000000 + msecond * 1000 + usecond; microsecond -= tv.tv_sec * 1000000 + tv.tv_usec; signal(SIGALRM,dealalarm); p_realt.it_value.tv_sec = microsecond / 1000000; p_realt.it_value.tv_usec = microsecond % 1000000; p_realt.it_interval.tv_sec = 0; p_realt.it_interval.tv_usec = 0; setitimer(ITIMER_REAL,&p_realt,NULL); while(!wakeupme); printf("wake time > %d-%d-%d %d:%d:%d:%d:%d\n",inputtime.tm_year+1900,inputtime.tm_mon+1,inputtime.tm_mday,inputtime.tm_hour,inputtime.tm_min,inputtime.tm_sec,msecond,usecond); return 0; } else if(argv[1][0] == '-' && argv[1][1] == 'c' && argc == 5) { printf("-----PART C-----\n"); //bind signal and function for parent process signal(SIGALRM,psig_real); signal(SIGVTALRM,psig_virtual); signal(SIGPROF,psig_prof); //init parent process 3 timer p_realt.it_interval.tv_sec = 9; p_realt.it_interval.tv_usec = 999999; p_realt.it_value.tv_sec = 9; p_realt.it_value.tv_usec = 999999; setitimer(ITIMER_REAL,&p_realt,NULL); p_virtt.it_interval.tv_sec = 9; p_virtt.it_interval.tv_usec = 999999; p_virtt.it_value.tv_sec = 9; p_virtt.it_value.tv_usec = 999999; setitimer(ITIMER_VIRTUAL,&p_virtt,NULL); p_proft.it_interval.tv_sec = 9; p_proft.it_interval.tv_usec = 999999; p_proft.it_value.tv_sec = 9; p_proft.it_value.tv_usec = 999999; setitimer(ITIMER_PROF,&p_proft,NULL); pid1 = fork(); if(pid1==0) { //bind signal and function for child1 process signal(SIGALRM,c1sig_real); signal(SIGVTALRM,c1sig_virtual); signal(SIGPROF,c1sig_prof); //init child1 process 3 timer c1_realt.it_interval.tv_sec = 0; c1_realt.it_interval.tv_usec = 999999; c1_realt.it_value.tv_sec = 0; c1_realt.it_value.tv_usec = 999999; setitimer(ITIMER_REAL,&c1_realt,NULL); c1_virtt.it_interval.tv_sec = 0; c1_virtt.it_interval.tv_usec = 999999; c1_virtt.it_value.tv_sec = 0; c1_virtt.it_value.tv_usec = 999999; setitimer(ITIMER_VIRTUAL,&c1_virtt,NULL); c1_proft.it_interval.tv_sec = 0; c1_proft.it_interval.tv_usec = 999999; c1_proft.it_value.tv_sec = 0; c1_proft.it_value.tv_usec = 999999; setitimer(ITIMER_PROF,&c1_proft,NULL); //get fibonacci fib = fibonacci(atoi(argv[2])); //get child1 3 time and fibnacci printf("Child1 fib = %ld\n", fib); getitimer(ITIMER_REAL,&c1_realt); printf("Child1 Real Time = %ld Sec : %ld Msec\n", c1_real_secs + 9-c1_realt.it_value.tv_sec,(999999-c1_realt.it_value.tv_usec)/1000); getitimer(ITIMER_VIRTUAL,&c1_virtt); printf("Child1 Virtual Time = %ld Sec : %ld Msec\n",c1_virtual_secs + 9-c1_virtt.it_value.tv_sec,(999999-c1_virtt.it_value.tv_usec)/1000); getitimer(ITIMER_PROF,&c1_proft); printf("Child1 Prof Time = %ld Sec : %ld Msec\n",c1_prof_secs + 9-c1_proft.it_value.tv_sec,(999999-c1_proft.it_value.tv_usec)/1000); exit(0); } else if((pid2=fork()) == 0) { //bind signal and function for child2 process signal(SIGALRM,c2sig_real); signal(SIGVTALRM,c2sig_virtual); signal(SIGPROF,c2sig_prof); //init child2 process 3 timer c2_realt.it_interval.tv_sec = 9; c2_realt.it_interval.tv_usec = 999999; c2_realt.it_value.tv_sec = 9; c2_realt.it_value.tv_usec = 999999; setitimer(ITIMER_REAL,&c2_realt,NULL); c2_virtt.it_interval.tv_sec = 9; c2_virtt.it_interval.tv_usec = 999999; c2_virtt.it_value.tv_sec = 9; c2_virtt.it_value.tv_usec = 999999; setitimer(ITIMER_VIRTUAL,&c2_virtt,NULL); c2_proft.it_interval.tv_sec = 9; c2_proft.it_interval.tv_usec = 999999; c2_proft.it_value.tv_sec = 9; c2_proft.it_value.tv_usec = 999999; setitimer(ITIMER_PROF,&c2_proft,NULL); //get fibonacci fib = fibonacci(atoi(argv[3])); //get child2 3 time and fibnacci printf("Child2 fib = %ld\n", fib); getitimer(ITIMER_REAL,&c2_realt); printf("Child2 Real Time = %ld Sec : %ld Msec\n",c2_real_secs+9-c2_realt.it_value.tv_sec,(999999-c2_realt.it_value.tv_usec)/ 1000); getitimer(ITIMER_VIRTUAL,&c2_virtt); printf("Child2 Virtual Time = %ld Sec : %ld Msec\n",c2_virtual_secs+9-c2_virtt.it_value.tv_sec,(999999-c2_virtt.it_value.tv_usec)/1000); getitimer(ITIMER_PROF,&c2_proft); printf("Child2 Prof Time = %ld Sec : %ld Msec\n",c2_prof_secs+9-c2_proft.it_value.tv_sec,(999999-c2_proft.it_value.tv_usec)/ 1000); exit(0); } else { //get fibonacci fib = fibonacci(atoi(argv[4])); //print parent 3 time and fibnacci printf("Parent fib = %ld\n", fib); getitimer(ITIMER_REAL,&p_realt); printf("Parent Real Time = %ld Sec : %ld Msec\n", p_real_secs + 9 - p_realt.it_value.tv_sec, (999999 - p_realt.it_value.tv_usec) / 1000); getitimer(ITIMER_VIRTUAL,&p_virtt); printf("Parent Virtual Time = %ld Sec : %ld Msec\n", p_virtual_secs + 9 - p_virtt.it_value.tv_sec, (999999 - p_virtt.it_value.tv_usec) / 1000); getitimer(ITIMER_PROF,&p_proft); printf("Parent Prof Time = %ld Sec : %ld Msec\n", p_prof_secs + 9 - p_proft.it_value.tv_sec, (999999 - p_proft.it_value.tv_usec) / 1000); //wait child process waitpid(pid1,NULL,0); waitpid(pid2,NULL,0); } } } //parent process signal function void psig_real(int signum) { p_real_secs += 10; } void psig_virtual(int signum) { p_virtual_secs += 10; } void psig_prof(int signum) { p_prof_secs += 10; } //child1 process signal function void c1sig_real(int signum) { c1_real_secs += 10; } void c1sig_virtual(int signum) { c1_virtual_secs += 10; } void c1sig_prof(int signum) { c1_prof_secs += 10; } //child2 process signal function void c2sig_real(int signum) { c2_real_secs += 10; } void c2sig_virtual(int signum) { c2_virtual_secs += 10; } void c2sig_prof(int signum) { c2_prof_secs += 10; } //get fibonacci by n long fibonacci(unsigned int n) { if(n == 1 || n == 2) return 1; return fibonacci(n-1)+fibonacci(n-2); } 题目要求和代码如上,在Ubuntu上编译运行后报段错误,请问该如何修改? 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览