一 题目介绍
Linux是开源操作系统。在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验,我们可以理解Linux系统处理系统调用的流程以及增加系统调用的方法。Linux系统提供了多达几百种的系统调用,为了唯一地标识每一个系统调用,Linux为每个系统调用都设置了一个唯一的编号,称为系统调用号;同时每个系统调用需要一个服务例程完成其具体功能。
每个系统调用都对应一个内核服务例程来实现该系统调用的具体功能,其命名格式都是以”sys_”开头。服务例程的原型声明则是在./include/linux/syscall.h中,通常都有固定的格式。
与普通函数一样,系统调用通常也需要输入/输出参数。当系统调用执行成功时,将返回服务例程的返回值,通常是0。但如果执行失败,为防止与正常的返回值混淆,系统调用并不直接返回错误码,而是将错误码放入一个名为errno的全局变量中,通常是一个负值,通过调用perror()库函数,可以把errno翻译成用户可以理解的错误信息描述。
系统调用必须仔细检查用户传入的参数是否合法有效。最重要的是要检查用户提供的指针是否有效,以防止用户进程非法访问数据。
题目内容:添加一个系统调用,实现对指定进程的nice值得修改或读取功能,并返回进程最新的nice值及优先级prio。
考察的知识点:在系统中根据需要添加新的系统调用。
问题的关键点:添加的系统调用实现对指定进程的nice值得修改或读取功能。
二 实验思路
添加系统调用的主要步骤为:
修改系统调用表声明系统调用服务例程原型实现系统调用服务例程
调用原型:int mysetnice(pid_t pid,int flag,int nicevalue,void _user*prio,void _user*nice);
pid:进程ID
flag:若值为0,则表示读取nice值;若值为1,则表示修改nice值。
nicevalue:为指定进程设置的新nice值。
prio、nice:指向进程当前优先级及nice值。
返回值:系统调用成功时返回0;失败时返回错误码EFAULT。
三 遇到问题及解决方法
1.提示权限不够
解决:命令行以su开头,或者首先输入sudo -s进入root模式。
2. 1 http://cn./ubuntu bionic/main amd64 make amd64 4.1-9.1ubuntu1
无法解析域名“cn.”
E: 无法下载 http://cn./ubuntu/pool/main/m/make-dfsg/make_4.1-9.1ubuntu1_amd64.deb 无法解析域名“cn.”
E: 有几个软件包无法下载,要不运行 apt-get update 或者加上 --fix-missing 的选项再试试?
解决:执行 apt-get update
E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用)
E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?
解决:ps -e | grep apt
738 ? 00:00:00 apt.systemd.dai
808 ? 00:00:00 apt.systemd.dai
1610 ? 00:00:00 apt.systemd.dai
root@eowyn-virtual-machine:~/linux-5.0.5# sudo kill 738
root@eowyn-virtual-machine:~/linux-5.0.5# sudo kill 808
root@eowyn-virtual-machine:~/linux-5.0.5# sudo kill 1610
dpkg: 错误: 正在解析文件 '/var/lib/dpkg/updates/0004' 第 0 行附近:
在字段名 #padding 中有换行符
解决: sudo rm /var/lib/dpkg/updates/*
sudo apt-get update
/boot/config-4.18.0-15-generic:5814:warning: symbol value 'm' invalid for HSA_AMD
Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
scripts/kconfig/Makefile:29: recipe for target 'menuconfig' failed
make[1]: *** [menuconfig] Error 1
Makefile:538: recipe for target 'menuconfig' failed
make: *** [menuconfig] Error 2
解决:全屏即可
四 核心代码及实验结果展示
实验一
当前版本:
进入root模式
清理残留的.config和.o文件
配置内核
编译内核,生成启动映像文件
编译模块
重启
查看当前内核版本
实验二
进入linux-5.0.7/arch/x86/entry/syscalls/syscall_64.tbl进行修改
按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出
进入include/linux/syscalls.h
按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出
进入kernel/sys.c
按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出
清除残留的.config和.o文件
配置内核
编译内核,生成启动映像文件
编译模块
重启
新建hello.c文件,输入
#define _GNU_SOURCE#include <unistd.h>#include <sys/syscall.h>#define __NR_mysyscall 337int main(){syscall(__NR_mysyscall);}
编译该程序,并进入日志查看
实验三(没有用书上的,自己的点子更加分)
在linux-4.18.19目录下输入vim arch/x86/entry/syscalls/syscall_64.tbl
按i进入插入模式
输入335 64 shellsort __x64_sys_shellsort
按esc退出插入模式,输入:wq强行保存退出
输入vim include/linux/syscalls.h
按i进入插入模式
输入asmlinkage long sys_shellsort(int n,void __user*s);
按esc退出插入模式,输入:wq强行保存退出
输入vim kernel/sys.c
按i进入插入模式
输入
SYSCALL_DEFINE2(shellsort,int,n,void __user*,s){int i, j, k;int temp, gap;int arr[100];/* static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n){if (access_ok(VERIFY_READ, from, n))n = __arch_copy_from_user(to, from, n);else /* security hole - plug it */memzero(to, n);return n;}该函数先通过access_ok检查参数中指向用户空间数据块的指针是否有效,然后通过__copy_from_user进行正式的拷贝。*to是内核空间的指针,*from是用户空间指针,n表示从用户空间向内核空间拷贝数据的字节数。如果拷贝成功,则返回0,否则返回还没有完成拷贝的字节数。*/copy_from_user((void *)arr,s,n*sizeof(int));for (gap = n / 2; gap > 0; gap /= 2) //步长的选取{for (i = 0; i < gap; i++) //直接插入排序原理{for (j = i + gap; j < n; j += gap) //每次加上步长,即按列排序。if (arr[j] < arr[j - gap]){temp = arr[j];k = j - gap;while (k >= 0 && arr[k] > temp) //记录后移,查找插入位置{arr[k + gap] = arr[k];k -= gap;}arr[k + gap] = temp; //找到位置插入}}}/*copy_to_user(void __user *to, const void *from, unsigned long n){might_sleep();BUG_ON((long) n < 0);if (access_ok(VERIFY_WRITE, to, n))n = __copy_to_user(to, from, n);return n;}该函数先通过access_ok检查参数中指向用户空间数据块的指针是否有效,然后通过__copy_to_user进行正式的拷贝。*to是用户空间的指针,*from是内核空间的指针,n表示从内核空间向用户空间拷贝数据的字节数。如果拷贝成功,则返回0,否则返回还没有完成拷贝的字节数。*/copy_to_user(s,(void *)arr,n*sizeof(int));}
按esc退出插入模式,输入:wq强行保存退出
然后编译内核,编译内核成功,输入uname -a显示现在的内核
编译完之后在主目录下新建文本文件shellsort.c
内容如下
#include<stdio.h>#include<unistd.h>#include<sys/syscall.h>int main(){int n;int i;int s[100];printf("请输入元素个数(不大于100)\n");scanf("%d",&n);printf("请依次输入需要排序的数组\n");for(i=0;i<n;i++){printf("[%d]",i+1);scanf("%d",&s[i]);}syscall(335,n,s);printf("经过shell排序之后的数组\n");for(i=0;i<n;i++){printf("[%d]",i+1);printf("%d\n",s[i]);}return 0;}
输入gcc -o shellsort shellsort.c
输入./shellsort
五 个人实验改进与总结
5.1 个人实验改进
在仔细研究了CSDN上的代码后,重新编写了用户态程序的逻辑结构,使用户在不需要修改nice值的情况下不需要输入nice值,即给nicevalue赋初值为0。内核态程序返回修改后的prio值,而不是进程最初的prio值。并且使代码更加精简。
5.2 个人实验总结
通过这次实验我熟知了很多Linux命令,知道了一些错误的解决方法以及查询如何解决问题的方法。同时也锻炼了我的耐心。
关于nice和prio的一些理解:
内核可以自行改变prio值(但不能改变nice值)。例如,如果进程占用过多CPU,则可能会降低进程的优先级;如果进程由于其他优先级较高的进程而无法长时间运行,则可能会增加进程的优先级。在这些情况下,内核将更改prio值,nice值保持不变,因此公式“PR = 20 + NI”将不正确。因此,NI值可以解释为内核提示进程应该具有的优先级,但内核可以根据情况自行选择真正的优先级prio。但通常是公式“PR = 20 + NI”是正确的。
nice的取值范围为-20~19,越小优先级越高。
set_user_nicevoid set_user_nice(struct task_struct *p, long nice)/*输入类型为task_struct的结构体*/{bool queued, running;/*分别为排队状态或运行状态的布尔值*/int old_prio, delta;/*分别为原优先级和参数*/struct rq_flags rf;/*运行队列的标志*/struct rq *rq;/*运行队列*/if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)/*-20~19*/return;rq = task_rq_lock(p, &rf); /*上锁*/update_rq_clock(rq);/*更新运行队列的时钟*/if (task_has_dl_policy (p) || task_has_rt_policy(p)) {/*判断是否是实时进程。其中task_has_dl_policy的函数为static inline int task_has_dl_policy(struct task_struct *p){return dl_policy(p->policy);}dl_policy的函数为static inline int dl_policy(int policy){return policy == SCHED_DEADLINE;}SCHED_DEADLINE宏定义值为6,即判断进程的prio值是否为6task_has_rt_policy与其类似,即判断prio值是否为1或2*/p->static_prio = NICE_TO_PRIO(nice);/*将nice值转化成prio值并赋值,实际上应该不可行。*/goto out_unlock;/*解锁*/}queued = task_on_rq_queued(p);/*排队状态*/running = task_current(rq, p);/*运行状态*/if (queued)dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);/*dequeue_task出队*/if (running)put_prev_task(rq, p);/*用另一个进程替代当前运行的进程之前调用*/p->static_prio = NICE_TO_PRIO(nice);/*将nice值转化成prio值并赋值。*/set_load_weight(p, true);/*计算符合权重*/old_prio = p->prio;/*旧的优先级*/p->prio = effective_prio(p);/*返回进程p的优先级,进程p可以使实时进程*/delta = p->prio - old_prio;/*动态优先级改变状况*/if (queued) {enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);/*将进程添加到队列*/if (delta < 0 || (delta > 0 && task_running(rq, p)))resched_curr(rq);/*标记当前进程需要被调度出去*/}if (running)set_curr_task(rq, p);/*在当前进程的调度策略发生变化时调用*/out_unlock:task_rq_unlock(rq, p, &rf);/*解锁*/}