前言
每一次OS作业(除了第一次放水的作业外),都是内心的淬炼…
添加内核模块
查询syscall_table的地址:
sudo cat /proc/kallsyms | grep sys_call_table
这里的 ffffffff8ae002e0 要记住(不同环境地址不一样),待会儿写代码要用。
先找一找可用的系统调用号,然后在cat一下看看。
sudo find / -name unistd_64.h
cat /usr/src/linux-headers-5.4.0-53-generic/arch/x86/include/generated/uapi/asm/unistd_64.h
这里我们可以看到436以后(包括436)的调用号系统没用上,不过当时做的时候没留意,选了个384。结果没啥问题,可能384不是关键的系统调用号吧。。懒得改了。(不过不建议读者这么搞
然后hello.c文件:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
#define SYS_CALL_TABLE_ADDRESS 0xffffffff8ae002e0 //sys_call_table对应的地址
#define NUM 384 //系统调用号为384
int orig_cr0; //用来存储cr0寄存器原来的值
unsigned long *sys_call_table_my=0;
static int(*anything_saved)(void); //定义一个函数指针,用来保存一个系统调用
static int clear_cr0(void) //使cr0寄存器的第17位设置为0(内核空间可写)
{
unsigned int cr0=0;
unsigned int ret;
asm volatile("movq %%cr0,%%rax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中
ret=cr0;
cr0&=0xfffffffffffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器
asm volatile("movq %%rax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中>,同时移动到寄存器cr0中
return ret;
}
static void setback_cr0(int val) //使cr0寄存器设置为内核不可写
{
asm volatile("movq %%rax,%%cr0"::"a"(val));
}
static int sys_mycall(void){
printk("*********** 2018*******");
printk("Andy Dennis");
return 0;
}
static int __init call_init(void)
{
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("call_init......\n");
anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系统调用表中的NUM位置上的系统调用
orig_cr0=clear_cr0();//使内核地址空间可写
sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系统调用替换NUM位置上的系统调用
setback_cr0(orig_cr0);//使内核地址空间不可写
return 0;
}
static void __exit call_exit(void)
{
printk("call_exit......\n");
orig_cr0=clear_cr0();
sys_call_table_my[NUM]=(unsigned long)anything_saved;//将系统调用恢复
setback_cr0(orig_cr0);
}
module_init(call_init);
module_exit(call_exit);
(注:下面路径和上面相比有点变化,是因为我把文件挪到 1 的文件下了)
sudo vim Makefile
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
mymodule-objs := hello.o
obj-m := hello.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
test:
gcc test.c -o test.o
./test.o
remod:
rmmod hello.ko
insmod hello.ko
lsmod | grep hello
endif
然后
sudo make
查看一下该目录下有啥:
ls
发现太多文件了,这里我们过滤一下:
ls | grep 'hello.*'
可以看到此时已经编译成功了。
插入模块:
sudo insmod hello.ko
查看是否插入成功
lsmod | grep hello
如果想卸载内核模块: (我说的是如果,不是必须的)
rmmod hello.ko
接着我们来写一下测试文件:
#include<stdio.h>
#include<stdlib.h>
#include<linux/kernel.h>
#include<sys/syscall.h>
#include<unistd.h>
int main()
{
unsigned long x = 0;
x = syscall(384); //测试384号系统调用
printf("Andy Dennis- syscall result: %ld\n", x);
return 0;
}
由于在Makefile环境我已经写了脚本,所以我这里直接
sudo make test
也可以下面两句,效果一样: (这里只不过是想说还可以将一些脚本写到Makefile中提高效率)
gcc test.c -o test.o
./test.o
查看一下内核输出信息:
dmesg
四种单机 IPC 方式
1.信号-signal
sudo vim message.c
然后写好下面的代码:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
int k1;
void int_fun1(){
k1=0;
}
main(){
int k, p1;
while((p1=fork())==-1);
if(p1>0){
// 子进程
for(k=1;k<4;k++){
printf("How are you!\n");
sleep(1);
}
kill(p1, 12);
wait(0);
printf("OK!\n");
exit(0);
} else {
// 父进程
signal(12, int_fun1); // 只是注册,等待子进程调用kill
k1=1;
while(k1==1){
printf("I am child\n");
sleep(1);
}
printf("Child exited!\n");
exit(0);
}
}
接着编译一下
sudo gcc message.c -o message.exe
可以看到有警告,但是不管了
反正是生成了message.exe.
运行message.exe
2.管程
sudo vim pipe.c
pipe.c文件中
#include<stdlib.h>
#include<string.h>
main(){
int p1, fd[2];
char outpipe[50];
char inpipe[50];
pipe(fd);
while((p1=fork())==-1);
if(p1==0){
strcpy(inpipe, "This is a message!");
write(fd[1], inpipe, 50);
exit(0);
} else {
wait(0);
read(fd[0], outpipe, 50);
printf("%s\n", outpipe);
exit(0);
}
}
然后编译一下
sudo gcc -c pipe.c -o pipe.exe
运行
./pipe.exe
3.消息队列
sudo vim sndfile.c
sndfile.c文件中:
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<linux/msg.h>
#define MAXMSG 512
struct my_msg{
long int my_msg_type;
int i;
char some_text[MAXMSG];
}msg;
main(){
int msgid;
char buffer[BUFSIZ];
// 创建消息队列
msgid = msgget(12, 0666|IPC_CREAT);
while(1){
puts("Enter some text:");
fgets(buffer, BUFSIZ, stdin);
msg.i++;
printf("i=%d\n", msg.i);
msg.my_msg_type=3;
strcpy(msg.some_text, buffer);
// 发送消息到消息队列
msgsnd(msgid, &msg, MAXMSG, 0);
if(strncmp(msg.some_text, "end", 3)==0)
break;
}
exit(0);
}
编译一下
sudo gcc sndfile.c -o sndfile.exe
再写个rcvfile.c
sudo vim rcvfile.c
rcvfile.c中:
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<linux/msg.h>
#define MAXMSG 512
struct my_msg{
long int my_msg_type;
int i;
char some_text[MAXMSG];
}msg;
main(){
int msgid;
msg.my_msg_type=3;
msgid=msgget(12, 0666|IPC_CREAT); // 获取消息队列
while(1){
// 接受消息
msgrcv(msgid, &msg, BUFSIZ, msg.my_msg_type, 0);
printf("You wrote:%s and i=%d\n",msg.some_text, msg.i);
if(strncmp(msg.some_text, "end", 3)==0)
break;
}
// 删除消息队列
msgctl(msgid, IPC_RMID, 0);
exit(0);
}
sudo gcc rcvfile.c -o rcvfile.exe
先运行一下sndfile.exe文件:
./sndfile.exe
4.共享内存
sudo vim sndshm.c
这里不得不说一下一个大坑,就是这里的shm.h的头文件,新版的应该是<sys/shm.h>, 旧版的是<linux/shm.h>。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/shm.h>
main(){
int shmid;
char *viraddr;
char buffer[BUFSIZ];
// 创建共享内存
shmid=shmget(1234, BUFSIZ, 0666|IPC_CREAT);
// 附接共享内存
viraddr=(char*)shmat(shmid, 0, 0);
while(1){
puts("Enter some text:");
fgets(buffer, BUFSIZ, stdin);
// 采用追加的方式写到共享内存
strcat(viraddr, buffer);
if(strncmp(buffer, "end", 3)==0)
break;
}
// 切断与共享内存的链接
shmdt(viraddr);
exit(0);
}
sudo vim rcvshm.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/shm.h>
main(){
int shmid;
char *viraddr;
// 创建共享内存
shmid=shmget(1234, BUFSIZ, 0666|IPC_CREAT);
// 附接到共享内存
viraddr=(char*)shmat(shmid, 0, 0);
printf("Your message is: %s", viraddr);
// 切断与共享内存的链接
shmdt(viraddr);
// 释放共享内存
shmctl(shmid, IPC_RMID, 0);
exit(0);
}