实验一:Linux内核编译及添加系统调用(HDU)

花了一上午的时间来写这个,良心制作,发现自己刚学的时候没有找到很详细的,就是泛泛的说了下细节地方也没有,于是自己写了这个,有点长,如果你认真的看完了,也应该是懂了。

一、前期准备工作

  1. 需要准备虚拟机上安装Ubuntu,笔者安装的是Ubuntu18.04,安装的教程自行百度解决,教程很多。有几点需要提一下,就是内存分配至少60G,核分配4个最好,为了在编译的时候别崩溃。

    建议去熟悉一下Linux下面的文件目录结构,根目录下每个目录一般会存放什么样的文件

    ,然后常见命令操作也要熟悉一下。
  2. 下载Linux内核地址,自行选择版本,建议选择4.xx版本,因为版本高出错的概率也大。

    下载好了之后,会放在自己的Ubuntu中的Downloads目录下,同时是一个压缩文件,到时候需要解压到放内核目录文件下。首先进入到该Downloads文件目录下,查看是否下载好了。
$cd ~/Downloads
$ls
linux-4.19.25.tar.xz

之后开始解压上面的那个压缩文件到存放内核的地方,就是Linux系统的/usr/src目录下,此目录用来存放内核源码的。从上图也可以了解到。

cd ~/Downloads
tar xvJf linux-4.19.25.tar.xz -C /usr/src

进入/usr/src目录查看是否有,如果有就可以开始后续工作了。

二、实验要求和内容

1. 内容要求:

(1)添加一个系统调用,实现对指定进程的nice值的修改或读取功能,并返回进程最新的nice值及优先级。建议调用原型是int mysetniec(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。

(2)写一个简单的应用程序测试(1)中添加的系统调用。

(3)若系统调用了Linux的内核函数,要求深入阅读相关的源码。

2. Linux系统调用的基本概念

实质是指调用内核函数,于内核态中运行,Linux中的用户通过执行一条访管指令“int $0x80”来调用系统调用,该指令会产生一个访管中断,从而让系统暂停当前的进程执行,而转去执行系统调用处理程序。通过用户态传入的系统调用号从系统调用表中找到相应的服务例程的入口并执行,完成后返回。

(1)系统调用号与系统调用表:Linux内核中设置了一张系统调用表,用于关联系统调用号及其相对应的服务例程入口地址,定义在./arch/x86/entry/syscalls/syscall_64.tbl文件中,每个系统调用占一个表项,一旦分配好就不可以有任何变更。

(2)系统调用服务例程:每个系统调用都对应一个内核服务例程来实现系统调用的功能,其命名的格式都是以"sys_开头。其代码通常放在./kernel/sys.c中,服务例程的原型声明则是放在./include/linux/syscall.h中。如sys_open,通常格式是asmlinkage long sys_open(int flag......)。其中的amslinkage是一个必需的限定词,用于通知编译器从堆栈中提取函数的参数,而不是从寄存器中。

在sys.c中编程时,格式是SYSCALL_DEFINE5(mysetnice, pid_t, pid, int, flag, int, nicevalue, void __user *, prio, void __user *, nice) N=5代表参数的个数。

(3)系统调用参数传递:在X86中,Linux通过6个寄存器来传入参数,其中一个eax是传递系统调用号,后面的5个传递参数。

(4)系统调用参数验证

三、开始实验

1. 切换到root权限下,防止权限不够,导致出错。

$ sudo passwd root
[sudo] password for leslie:
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
$ su root
Password:

(1)首先,你安装Linux系统时,它会让你设置一个你的用户名和用户密码,在这里我设置的用户名是leslie,即绿色字体leslie@tp50的前半部分,后半部分tp50是我的主机名。

sudo放在命令首,意思是当前指令以管理员权限运行。

(2)passwd是一条命令,用来修改用户密码,参数root是超级用户名,拥有系统最高的权限。passwd root的意思是修改超级用户的密码,在创建Ubuntu时,默认超级用户是没有密码的(也可能是一个随机数之类的我不记得了),用这条命令重新设定一个密码。

(3)su是一条命令,用来切换当前用户,在第一章你会认识到,Linux是一个多用户多任务的操作系统。参数root是用户名,指示切换到的用户,在这里su root意为切换到root用户,当参数缺省时,默认切换到超级用户。

(4)你会发现,它在提示输入密码时,虽然键盘已经输入了密码,但是终端没有任何响应,不要担心,这正是Unix和Linux的特点,为了确保安全,在输入密码时不显示输入的内容,在输入密码后,直接按下回车就好了。

2. 分配系统调用号,修改系统调用表

(1)查看系统调用表,并修改

gedit /usr/src/linux-4.19.25/arch/x86/entry/syscalls/syscall_64.tbl

你只需要将linux-4.19.25换成你自己下载好的版本即可。

你会看见这个格式



应用二进制接口分为三种:64、x32和common,即三种不同的调用约定,这里不需考虑太多,三种任意选择一种即可,按照上述格式编写新的系统调用表表项如下:

335	64	first_compile		__x64_sys_first_compile



(2)声明系统调用服务例程

查看系统调用头文件

gedit  /usr/src/linux-4.19.25/include/linux/syscalls.h

同样将linux-4.19.25换成你自己的版本即可。



(3)实现自己的系统调用服务例程

首先进入解压后的文件目录,就是开始解压放入的目录,如下图:

$cd /usr/src/linux-4.19.34(换成自己的版本即可)/kernel
vim sys.c



函数说明:

这一步与上一步的关系,就是C语言中头文件与实现文件的关系,上一步我们对函数进行了声明,这里给函数一个具体的实现。

首先要明确,我们要实现一个什么样的功能,根据内容要求可知,这个系统调用需要具备对指定进程的nice值的修改及读取的功能,同时返回进程最新的nice值及优先级prio。

把功能分拆成一个一个小块,我们需要做到的有以下几点:

根据进程号pid找到相应的进程控制块PCB(因为进程控制块中记录了用于描述进程情况及控制进程运行所需要的全部信息,nice值和优先级正是其中的一部分);

根据PCB读取它的nice值和优先级prio;

根据PCB对相应进程的nice值进行修改;

将得到的nice值和优先级prio进行返回。


SYSCALL_DEFINE5(first_compile, pid_t, pid, int, flag, int, nicevalue, void __user *, prio, void __user *, nice)
{
int cur_prio, cur_nice;
struct pid *ppid;
struct task_struct *pcb; ppid = find_get_pid(pid); pcb = pid_task(ppid, PIDTYPE_PID); if (flag == 1)
{
set_user_nice(pcb, nicevalue);
}
else if (flag != 0)
{
return EFAULT;
} cur_prio = task_prio(pcb);
cur_nice = task_nice(pcb); copy_to_user(prio, &cur_prio, sizeof(cur_prio));
copy_to_user(nice, &cur_nice, sizeof(cur_nice)); return 0;
}

(4)开始编译内核

首先,用下面这条命令查漏补缺,很有用处,用来它,我编译是一次通过的,没有遇见什么其他麻烦。

sudo apt-get install libncurses5-dev make openssl libssl-dev bison flex

然后定位到,源码在的目录,也就是解压后放的目录。



然后运行命令

make menuconfig



然后会出现以下界面,根据下面的图片所指的按钮来,通过左右键来确定光标停在选的按钮上,enter键是确定键









准备工作做好后,开始编译,耗时最长

sudo make -j4 2> error.log

-j4表示使用四线程进行编译,这个过程大概持续一个小时,后面的重定向将错误信息输出到了error.log这个文件里面,方便我们之后进行错误排查,不至于一两个小时坐在电脑面前盯着信息输出生怕出现一个错误而自己错过了,之后修改只能靠两眼排查,相信我,那不是一种好的体验。

开始等待吧,结束后就可以安装内核了。

(5)安装内核

此时还是在你原来的目录路径下

安装模块:

sudo make modules_install

使用这一行命令进行模块的安装,模块的安装持续时间大概在十几分钟左右,视你分配的资源多寡这个时间会适当地增加或减少。

结束后

安装内核:

sudo make install

使用这一行命令进行内核的安装,内核的安装持续时间大概是几分钟,视你分配的资源多寡这个时间会适当地增加或减少。

这一步完成且没有任何错误后,恭喜你,你已经完成了绝大多数的工作了,剩下的都是一些简单且容易调试的内容,重启你的电脑/虚拟机。

这个时候可以查看你的/lib/module目录下有无安装好的内核了。



(6)重启系统

查看内核版本的命令,如下,你可以看自己现在的版本是否是新安装的内核

uname -a

之后可能会弹出这个界面,选择你自己刚刚编译好的内核即可



(7)测试

自己选择一个文件夹,存放自己的测试代码,创建.c文件

vim test.c

#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#define _SYSCALL_MYSETNICE_ 335
#define EFALUT 14 int main()
{
int pid, flag, nicevalue;
int prev_prio, prev_nice, cur_prio, cur_nice;
int result; printf("Please input variable(pid, flag, nicevalue): ");
scanf("%d%d%d", &pid, &flag, &nicevalue); result = syscall(_SYSCALL_MYSETNICE_, pid, 0, nicevalue, &prev_prio,
&prev_nice);
if (result == EFALUT)
{
printf("ERROR!");
return 1;
} if (flag == 1)
{
syscall(_SYSCALL_MYSETNICE_, pid, 1, nicevalue, &cur_prio, &cur_nice);
printf("Original priority is: [%d], original nice is [%d]\n", prev_prio,
prev_nice);
printf("Current priority is : [%d], current nice is [%d]\n", cur_prio,
cur_nice);
}
else if (flag == 0)
{
printf("Current priority is : [%d], current nice is [%d]\n", prev_prio,
prev_nice);
} return 0;
}

之后使用gcc进行编译,根据要求输入对应的值。

结束了。到这一步,你就熟悉了过程了,对于编译和添加内核有个基本的了解了。

最后附上需要用到的内核源码截图。

Linux源码地址

第一张图和第二张图是一个函数set_user_nice()



task_on_rq_quened()



task_current()

effective_prio()







Lab1:Linux内核编译及添加系统调用(详细版)的更多相关文章

  1. (转)Linux内核之进程和系统调用

    Linux内核之进程和系统调用 什么是系统调用 在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的.功能十分强大的一系列的函数.这些系统调用是在内核中实现的,再通过一定 ...

  2. Linux内核编译与安装

    2013-04-16    Linux内核介绍  Linux内核是一个用C语言写成的,符合POSIX标准的类Unix操作系统.内核是操作系统中最基本的一部分,提供了众多应用程序访问计算机硬件的机制.L ...

  3. Linux内核编译、安装流程

    原文链接:https://blog.csdn.net/qq_28437139/article/details/83692907 此处只讲linux内核编译步骤至于安装虚拟机,安装ubuntu操作系统请 ...

  4. Linux内核编译完整过程

    Linux内核编译完整过程 通过网上的资料我自己的实际内核编译,我把对Linux内核编译的过程写在这里,也许对其他的Linux爱好者的编译学习有些帮助,其中很大部分是网上的资料,另外就是我在实际编译过 ...

  5. Linux 内核 编译模块

    背景: 由于调试内核或者由于分区大小限制,有时候内核组件不一定完全需要编进内核中. 所以,在开发中经常将内核组件编译成为模块,等到在恰当的时机加载. 概览: Linux内核模块的编译方法有两种: 1. ...

  6. Linux内核编译配置脚本

    环境 宿主机平台:Ubuntu 16.04.6 目标机:iMX6ULL Linux内核编译配置脚本 在linux开发过程中熟练使用脚本可以大大简化命令行操作,同时对于需要经常重复操作的指令也是一种备忘 ...

  7. 运行在TQ2440开发板上以及X86平台上的linux内核编译

    一.运行在TQ2440开发板上的linux内核编译 1.获取源码并解压 直接使用天嵌移植好的“linux-2.6.30.4_20100531.tar.bz2”源码包. 解压(天嵌默认解压到/opt/E ...

  8. linux内核编译环境配置

    linux内核编译环境配置 如果不是编译内核,只需要安装与内核相匹配的kernel-devel开发包即可.即是/lib/modules/`uname -r`/build -> /usr/src/ ...

  9. 愚人的linux内核2440移植札记(超曲折版)

    http://blog.csdn.net/dreambegin/article/details/6904822 原来文章叫--编译内核之初体验.后来想了想,这篇文章让我体验了好多遍.不该叫这么大气的名 ...

随机推荐

  1. 【MySQL】MariaDB10.3新特性--闪回查询

    MariaDB10.3新特性--闪回查询 System-Versioned表特性的引入,可以对表进行闪回.完成类似于Oracle的闪回查询. 修改已有表为System-Versioned MariaD ...

  2. Linux(01):linux的起源、应用场景和学习目标

  3. golang学习笔记---string && strconv

    1.字符串的组成?Golang的字符串都是由单个字节连接起来的,每个字节都是UTF8编码标识的Unicode文本.(不需要在考虑中文不兼容问题) 2.如何遍历字符串?先看一个例子: package m ...

  4. Python 3 的 int 类型详解(为什么 int 不存在溢出问题?)

    在以前的Python2中,整型分为int和long,也就是整型和长整型, 长整型不存在溢出问题, 即可以存放任意大小的数值,理论支持无限大数字. 因此在Python3 中,统一使用长整型,用int表示 ...

  5. IdentityService4学习笔记之Client Credentials

    IdentityService4简介 一套为应用程序构建身份认证和访问控制解决方案/框架,包括单点登录,身份认证,授权和API访问控制. 前文 今天介绍ClientCredentials认证类型,适用 ...

  6. Loadrunner 11.00 初始化失败; 通信错误。 Error (-81024): LR_VUG: The 'WS_SOAP' type is not supported on 'WIN32' platforms .

    搜索LR安装目录bin文件夹下有个“wlrun.exe”的文件,邮件点击“属性”->"兼容性"->兼容模式中选择“windows 7”,确认后重新打开即可,win10下 ...

  7. Mac系统docker初探

    最近把工作环境要切到mac中,由于一直想看看docker是怎么回事,以前在win和linux下面都没有用起来,这次在mac中决定试一把,尝试下新的环境部署方式. 安装docker mac中,直接有类似 ...

  8. python高级编程——入门语法(一)

    元类 在python中一切皆对象,类也是对象,只不过类是一个创建对象的对象,我们可以在类中动态的创建一个类,比如 def func(name): if name == "Plane" ...

  9. 【开发笔记】- MySQL中limit查询超级慢,怎么办?

    有如下解决方法: (1).通过判断id的范围来分页 limit ; 也得到了分页的数据,但是我们发现如果id不是顺序的,也就是如果有数据删除过的话,那么这样分页数据就会不正确,这个是有缺陷的. (2) ...

  10. em与rem之间的区别以及移动设备中的rem适配方案

    em与rem之间的区别: 共同点: 它们都是像素单位 它们都是相对单位 不同点: em大小是基于父元素的字体大小 rem大小是基于根元素(html)的字体的大小 实例: <!DOCTYPE ht ...