杭电操作系统实验一报告

为什么要发布我的报告?
按理说操作系统实验应该自己做,这样能锻炼自己。鉴于我的报告还是比较有参考价值,能让以后的同学参考一下,就做成md的形式。仅供参考!

杭电操作系统实验一报告

实验一报告

一、实验内容:

(1)实验名:Linux内核编译及添加系统调用报告
(2)实验要求:
1)编译内核
2)添加系统调用

二、实验思路

(1)内核编译思路:使用极客云make,提取编译后的文件放入虚拟机修改grub
(2)修改进程优先级思路:附录中的Demo1每行都有注释。先使用find_get_pid()函数根据进程号pid得到kpid指针,再使用pid_task()函数得到task_struct(其实就是pcb)。set_user_nice()修改nice值,task_nice()和task_prio()重新取得nice值和prio值(优先级)。最后将内核态的变量值转换为用户态,从而显示出来。

三、编译内核实验步骤及问题

本次实验我使用极客云进行内核编译,价格良心,编译速度非常快,12核的机器大约10~15分钟可以编译完,客服说使用的是容器。由于是云服务器,无法修改grub。

(1)下载内核,下载完使用命令解压。
遇到的问题及解决方法:下载内核版本最好选旧版,测试4.18可行,下载最新版发现无法修改grub。

(2)按书上的安装make、ncurses等包,按书上一步一步运行命令。遇到简单报错只需安装报错提示的包即可。

遇到的问题及解决方法:提示使用的gcc版本过低,下载最新的gcc-8版本,发现还是会调用老版本的gcc。将gcc-8改为gcc放在对应目录(cd /user/bin)下,再试还是不可以。询问同学后,创建软连接ln -s gcc-8 gcc后成功解决。

(3)使用make -j 表示使用最大核心数进行编译。

(4)由于无法无法进行sudo apt-get install grub
解决方法:
只能看看编译产生了什么文件,复制到虚拟机上,经过手动查找和实验发现两个文件夹:
/lib/modules 中有个4.19.0(内核库文件)
/boot全部文件(启动的核心档案和内核映像)

把它们复制到虚拟机的对应目录。在虚拟机上运行sudo apt-get install grub,经测试可以成功。

四、添加系统调用实验步骤及问题

(1)分配系统调用号
添加内容:336 64 mychangenicesyscall __x64_sys_change_nice_syscall
目录位置:vim linux-4.19/arch/x86/entry/syscalls/syscall_64.tbl
遇到的错误及解决方法:由于添加位置错误,导致调用号写错,最终结果也错误。注意不要放在最后,放在合适的位置,起系统调用名也要和后面的操作对应起来。

遇到的问题及解决方法:arch/x86/entry/syscall_64.o:(.rodata+0x1120):对‘sys_zwhsyscall’未定义的引用。这是由于系统调用按其他调用的格式来写,服务例程入口加个__x64_

(2)申请系统调用服务例程原型
添加内容:
int change_nice_syscall(pid_t pid, int flag, int nicevalue,void_userprio,void_usernice)
遇到的错误及解决方法:由于添加位置不对导致出错,之后选择把它放到最后面。

(3)写一个修改指定进程优先级函数。见附录Demo1
遇到的错误及解决方法:由于添加位置不对导致出错,之后选择把它放到最后部分。

(4)测试代码:使用附录中的Demo2进行测试。

测试方法:

1)打开火狐浏览器,使用命令ps -el或者正则匹配一下和火狐有关的进程ps -el | grep fire,查到火狐浏览器的相关进程号pid。
2)使用top -p pid,得到进程详细信息,如nice值。
3)使用程序修改nice值,之后再使用top命令验证。

五、实验总结

系统调用代码参考:
https://blog.****.net/babybabyup/article/details/79839734#comments,这是杭电同级的软工同学的博客,但是我发现他的代码好多错误,例如没有重新取得当前进程的prio值导致代码显示错误,我在我的代码中改正了他的错误。
细究实验一,其实有很多知识点,例如:
(1)SYSCALL_DEFINE5之所以叫SYSCALL_DEFINE5是因为它有五个参数,并不是随便取名字的。

(2)在/include/linux/sched/prio.h中我们可以找到如下定义:

#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO		MAX_USER_RT_PRIO
#define MAX_PRIO		(MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO		(MAX_RT_PRIO + NICE_WIDTH / 2)

非常清楚地写明linux的prio的范围。
其中,实时进程(sched policy)的优先级取值范围是099,非实时进程的取值范围为100139。
优先级转nice值时,通过当前task的静态优先级的值减去DEFAULT_PRIO。DEFAULT_PRIO的值是120。

(3)task_struct是个值得一看的数据结构,而且后面的实验也要用到这个数据结构。来自:linux-2.6.30/include/linux/sched.h。
优先级方面,它有四个优先级:

struct task_struct {
...
    int				prio;//优先级
	int				static_prio;//静态优先级
	int				normal_prio;//归一化优先级,对于非实时进程,normal_prio 的值就等于静态优先级值 static_prio;对于实时进程,normal_prio = MAX_RT_PRIO-1 - p->rt_priority
	unsigned int			rt_priority;//实时进程优先级,取值0-99,值为0,则非实时进程。
...
}

task_struct 中的成员变量 prio越小,进程的优先级越高。prio 值的取值范围为0~139。

(4)task_prio()源码也很好理解:

int task_prio(const struct task_struct *p)
{
	return p->prio - MAX_RT_PRIO;//ps 命令得到的priority 值也是 p->prio - 100 得到的!!!
}

得到的是相减的结果。
(5)
如果nice过大或过小,都会被系统降到最大合法值或提高到最小合法值。
nice范围从-20~19,其中-20最高,19最低,只有系统管理者可以设置负数的等级。
当我们更改nice值时,系统不会对错误的nice值做响应。

为什么是这样呢?
需要看set_user_nice()的源码。

void set_user_nice(struct task_struct *p, long nice)
{
...
	//如果当前task的nice值已经等于要设置的nice值,就直接退出
    //从这里可以看出nice值的范围在-20~19 之间
	if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)
		return; 
...
}

当nice值非法,直接return。

六、附录

(1)Demo1

SYSCALL_DEFINE5(mysetnice,pid_t,pid,int,flag,int,nicevalue,void __user*,prio,void __user*,nice){
    struct pid * kpid;/*进程描述符指针,指向一个枚举类型*/
    struct task_struct * task;/*任务描述符信息*/
    kpid = find_get_pid(pid);/* 根据进程号返回kpid,并增加引用次数count+1*/
    /*当引用计数为0时,当前进程就可以被抢占.*/
    task = pid_task(kpid, PIDTYPE_PID);/* 返回task_struct */
    int n;
    n = task_nice(task);/* 返回进程当前nice值 */
    int p;				/*这里linux内核编译时建议,先声明后赋值,不建议一起做*/
    p = task_prio(task);/*返回进程当前prio值*/
    if(flag == 1)
    {
        set_user_nice(task, nicevalue);/* 修改进程nice值 */
        n = task_nice(task);/*重新取得进程nice值*/
        p = task_prio(task);/*重新取得进程当前prio值*/
        copy_to_user(nice,&n,sizeof(n));/*将nice值拷贝到用户空间*/
        copy_to_user(prio,&p,sizeof(p));/*将prio值拷贝到用户空间*/
        return 0;  
    }
    else if(flag == 0)
    {
        copy_to_user(nice,&n,sizeof(n));/*将nice值拷贝到用户空间*/
        copy_to_user(prio,&p,sizeof(p));/*将prio值拷贝到用户空间*/
        return 0;
    }
    return EFAULT;

}

(2)Demo2

#define _GNU_SOURCE
#include <unistd.h>
#include<sys/syscall.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
    pid_t pid;
    int nicevalue;
    int flag;
    int p = 0;
    int n = 0;
    int *prio;
    int *nice;
    prio = &p;
    nice = &n;

    printf("请输入pid:\n");
    scanf("%d",&pid);
    printf("请输入nice:\n");
    scanf("%d",&nicevalue);

    printf("请输入flag:\n");
    scanf("%d",&flag);

    syscall(336,pid,flag,nicevalue,prio,nice);

    printf("现在的nice为%d\n,prio为%d\n",n,p);
    return 0;
}