杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)

一 题目介绍

Linux是开源操作系统。在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验,我们可以理解Linux系统处理系统调用的流程以及增加系统调用的方法。Linux系统提供了多达几百种的系统调用,为了唯一地标识每一个系统调用,Linux为每个系统调用都设置了一个唯一的编号,称为系统调用号;同时每个系统调用需要一个服务例程完成其具体功能。

每个系统调用都对应一个内核服务例程来实现该系统调用的具体功能,其命名格式都是以”sys_”开头。服务例程的原型声明则是在./include/linux/syscall.h中,通常都有固定的格式。

与普通函数一样,系统调用通常也需要输入/输出参数。当系统调用执行成功时,将返回服务例程的返回值,通常是0。但如果执行失败,为防止与正常的返回值混淆,系统调用并不直接返回错误码,而是将错误码放入一个名为errno的全局变量中,通常是一个负值,通过调用perror()库函数,可以把errno翻译成用户可以理解的错误信息描述。

系统调用必须仔细检查用户传入的参数是否合法有效。最重要的是要检查用户提供的指针是否有效,以防止用户进程非法访问数据。

 

题目内容:添加一个系统调用,实现对指定进程的nice值得修改或读取功能,并返回进程最新的nice值及优先级prio。

考察的知识点:在系统中根据需要添加新的系统调用。

问题的关键点:添加的系统调用实现对指定进程的nice值得修改或读取功能。

 

二 实验思路

添加系统调用的主要步骤为:

  1. 修改系统调用表
  2. 声明系统调用服务例程原型
  3. 实现系统调用服务例程

调用原型: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.archive.ubuntu.com/ubuntu bionic/main amd64 make amd64 4.1-9.1ubuntu1

  无法解析域名“cn.archive.ubuntu.com”

E: 无法下载 http://cn.archive.ubuntu.com/ubuntu/pool/main/m/make-dfsg/make_4.1-9.1ubuntu1_amd64.deb  无法解析域名“cn.archive.ubuntu.com”

E: 有几个软件包无法下载,要不运行 apt-get update 或者加上 --fix-missing 的选项再试试?

解决:执行 apt-get update

  1.  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

  1. dpkg: 错误: 正在解析文件 '/var/lib/dpkg/updates/0004' 第 0 行附近:

 在字段名 #padding 中有换行符

解决: sudo rm /var/lib/dpkg/updates/*

sudo apt-get update

  1. /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内核编译及添加系统调用(完整实验报告)_第1张图片

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第2张图片

编译内核,生成启动映像文件

编译模块

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第3张图片

 

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第4张图片

重启

查看当前内核版本

 

实验二

进入linux-5.0.7/arch/x86/entry/syscalls/syscall_64.tbl进行修改

按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第5张图片

进入include/linux/syscalls.h

按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出

 

进入kernel/sys.c

按i进入插入模式,按esc退出插入模式,按:wq强制保存并退出

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第6张图片

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第7张图片

清除残留的.config和.o文件

配置内核

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第8张图片杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第9张图片

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第10张图片

编译内核,生成启动映像文件

编译模块

重启

新建hello.c文件,输入

#define _GNU_SOURCE

#include 

#include 

#define __NR_mysyscall 337

int main()

{

syscall(__NR_mysyscall);

}

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第11张图片

编译该程序,并进入日志查看

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第12张图片

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第13张图片

 

 

实验三(没有用书上的,自己的点子更加分)

在linux-4.18.19目录下输入vim arch/x86/entry/syscalls/syscall_64.tbl

按i进入插入模式

输入335       64          shellsort               __x64_sys_shellsort

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第14张图片

按esc退出插入模式,输入:wq强行保存退出

输入vim include/linux/syscalls.h

按i进入插入模式

输入asmlinkage long sys_shellsort(int n,void __user*s);

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第15张图片

按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

#include

#include

int main()

{

       int n;

    int i;

       int s[100];

       printf("请输入元素个数(不大于100)\n");

       scanf("%d",&n);

       printf("请依次输入需要排序的数组\n");

       for(i=0;i

输入gcc -o shellsort shellsort.c

输入./shellsort

杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)_第16张图片

 

五 个人实验改进与总结

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_nice

void 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值是否为6
task_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);
/*解锁*/
}

 

你可能感兴趣的:(linux,操作系统,系统调用,编译内核)