操作系统实验一 添加系统调用实现对value值读写及关闭线程
操作系统:ubuntu
内核版本:kernel5.5.5
一 整体思路
计算机分为用户态和内核态两种状态,用户态是上层应用程序的活动空间,被限制不能直接接触底层。内核态直接控制硬件,对用户态提供运行环境。二者之间有访管控制,不允许直接访问。如果从用户态进入内核态,就需要系统调用。系统调用就是一系列的用户态和内核态的接口函数。
自己添加系统调用,需要三步。
一、在系统调用表里添加系统调用号
二、声明系统调用服务例程原型
三、实现系统调用函数
修改内核代码完成后,编译重启,就可以调用自己的系统调用函数了。
实现的调用函数功能:传入pid,可以读取进程的value和prio值,可以修改value值,可以杀死进程。
二 具体实现
我的内核版本是5.5.5,所以内核根目录在usr/src/linux-5.5.5,进入目录操作
一、在系统调用表里添加系统调用号
系统调用表是./arch/x86/entry/syscalls/syscall64.tbl
在表尾部添加系统调用号
未被占用的调用号 x32/x64/common 系统调用名 服务例程入口
550 common mysetprocess __x64_sys_mysetprocess
二、
服务例程原型声明在 ./include/linux/syscalls.h
asmlinkage int sys_mysetprocess(pid_t pid, int flag, int nicevalue,void __user * prio, void __user * nice);
五个参数:
pid:操作线程的pid
flag: 操作码,读取value值0,修改value值1,杀死进程2
nicevalue:要修改的value值
prio,nice:调用copy_to_user()函数把内核态读取到的prio,nice传递给用户
三、实现系统调用函数
在./kernel/sys.c中实现系统调用函数
SYSCALL_DEFINE5(mysetprocess,pid_t,pid, int, flag, int, nicevalue,void __user *, prio, void __user *, nice){
if(find_get_pid(pid)==NULL){
printk("input pid error.");
return EFAULT;
}
struct pid * kpid;
struct task_struct * task;
kpid = find_get_pid(pid);
task = pid_task(kpid,PIDTYPE_PID);
int cur_nice;
int cur_prio;
cur_nice=task_nice(task);
cur_prio=task_prio(task);
if(flag==1){
//set process's nice
set_user_nice(task,nicevalue);
cur_nice=task_nice(task);
cur_prio=task_prio(task);
printk("the nice value is changed!\n");
printk("current nice is %d and piro is %d.\n",cur_nice,cur_prio);
}else if(flag==0){
//get process's nice
printk("current nice is %d and piro is %d.\n",cur_nice,cur_prio);
}else if(flag==2){
//safely kill process by pid
kill_pid(kpid,SIGTERM,1);
printk("process is killed safely.\n");
return 0;
}else{
printk("operate failed.\n");
return EFAULT;
}
copy_to_user(prio,&cur_prio,sizeof(cur_prio));
copy_to_user(nice,&cur_nice,sizeof(cur_nice));
return 0;
}
使用的函数简单说明
find_get_pid(pid_t nr)
根据进程号nr得到进程描述符struct pid
pid_task(struct pid* pid,enum pid_type)
PIDTYPE_PID表示进程的进程号这一类型
根据进程描述符返回任务描述符
task_nice(const struct task_struct *p ),nice范围-20-19
返回进程nice值
task_prio(const struct task_struct *p)
返回进程prio值
set_user_nice(struct task_struct *p, long nice)
设置进程的nice值
kill_pid(struct pid *pid, int sig, int priv)
sig设为SIGTERM程序结束信号,SIGTERM可以被阻塞和处理,要程序正常退出。
priv设为1,发送SEND_SIG_PRIV信号,
发送进程描述符,结束信号,杀死进程
copy_to_user(void __user *to, const void *from, unsigned long n)
从内核空间拷贝内容到用户空间
n表示拷贝空间大小
四、编译内核
编译内核耗时长(单核的话要约两小时),占用空间大(至少准备30G空间),要耐心等待
在linux-5.5.5目录下
make menuconfig 选择save后exit
make -j4 (推荐使用多核编译,快一倍左右,虚拟机分配核数越多参数可以更大)
make modules_install
make install
使用uname -r 查看当前内核版本,判断是否编译成功
make clean 清理编译文件(一定要用,清理之前编译的占用空间)
重启
如果编译有问题,推荐去看这篇文章添加链接描述,介绍的很详细
五、测试系统调用
编写c程序,测试系统调用是否添加成功
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
int main(){
int pid,flag,nicevalue,prio,nice;
char control;
printf("请输入pid:\n");
scanf("%d",&pid);
printf("读入:0,修改:1, 杀死: 2 \n");
scanf("%d",&flag);
if(flag!=0&&flag!=1&&flag!=2){
printf("输入错误");
return 1;
}
if(flag==0){
int res = syscall(550,pid ,flag,0,&prio,&nice);
if(res!=0){
printf("进程不存在!\n");
return 1;
}
printf("进程的nice值为%d,prio值为%d\n",nice,prio);
}else if(flag==1){
printf("请输入要修改的nice值:\n");
scanf("%d",&nicevalue);
int res = syscall(550,pid,flag,nicevalue,&prio,&nice);
if(res!=0){
printf("进程不存在!\n");
return 1;
}
printf("修改成功!\n");
printf("进程的nice值为%d,prio值为%d\n",nice,prio);
}else if(flag==2){
int res = syscall(550,pid,flag,0,&prio,&nice);
if(res!=0){
printf("进程不存在!\n");
return 1;
}
printf("杀死进程成功.\n");
}
return 0;
}
运行测试:
dmesg里的日志记录
成功!