以前学习计算机操作系统的时候也学习过系统调用的三层机制,但是当时都是纯理论学习,没有亲身实践,很多都理解的比较模糊,这里借助老师的方法使用内嵌汇编加深理解。
要想理解系统调用的具体含义,我们需要先了解用户态、内核态和中断三个概念。简单的来说:
在用户态下,我们可以运行用户态进程,而在内核态下,我们不仅仅可以运行用户态下的进程,还可以运行更高级别的内核态进程。如果在用户态下我们需要使用内核态下的进程,那么我们可以借助中断操作来从用户态进入内核态。
上述的用户态可能概念比较模糊,这里我们举个栗子:
#include<stdio.h>
int main(){
int input,output,temp;
input=1;
temp=0;
output=input;
printf("%d%d",temp,output);
return 0;
}
#include<stdio.h>
int main(){
int input,output,temp;
input=1;
asm volatile(
"movl $0, %%eax\n\t"
"movl %%eax, %1\n\t"
"movl %2, %%eax\n\t"
"movl %%eax, %0\n\t"
:"=m"(output),"=m"(temp)
:"r"(input)
:"eax"
);
printf("%d%d",temp,output);
return 0;
}
这个例子是书上用来讲解内嵌汇编代码的写法,这里我们就不再过多讨论。首先我们观察C语言下的代码,我们发现这就是个简单的赋值操作,核心代码为:
temp=0;
output=input;
观察其汇编代码,我们得知这里使用了eax寄存器,将0和intput变量值传递给内存中的temp和output,那么我们把这个过程叫做用户态。可以看到,这里的赋值操作无需借助任何的高级权限,申明变量之后直接赋值就好了。那么什么叫做内核态?
这里我们依然以具体的C语言代码为例。
#include<stdio.h>
#include<time.h>
int main()
{
time_t tt;
struct tm *t;
__asm__ __volatile__(
"movl $0, %%ebx\n\t"
"movl $0xd, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0\n\t"
:"=m"(tt)
);
t = localtime(&tt);
printf("time:%d/%d/%d\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday);
return 0;
}
当你的程序需要使用到系统函数time()的时候,我们把这个系统函数以及它的执行过程叫做内核态,因为它使用了高级别指令time。当然,这里的
"movl $0, %%ebx\n\t"
"movl $0xd, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0\n\t"
:"=m"(tt)
语句就包含了从用户态使用系统调用这一特殊中断陷入内核态的整个过程。
想要从用户态进入到内核态不是平白无故就能实现的,这里我们需要借助中断的力量。这里我们仅仅讨论系统调用这一特殊中断。
cs:eip
&ss:esp
&eflags(current)
至内核栈中,然后将系统调用的中断服务程序的入口加载到cs:eip中,把当前的内核态ss:esp也加载到cpu中。这样,当前cpu的下一条指令即为中断程序的入口。在linux中使用int 0x80
语句来触发系统调用的执行,即执行中断向量0x80所对应的服务system_call
。restore_all & INTERRUPT_RETURN
中断结束后,执行restore_all & INTERRUPT_RETURN,此时将pop之前存储的用户态的cs:eip
&ss:esp
&eflags(current)
,从而恢复到之前的用户态中。
至此,系统调用过程就结束了。
#include<stdio.h>
#include<time.h>
int main()
{
time_t tt;
struct tm *t;
tt = time(NULL);
t = localtime(&tt);
printf("time:%d/%d/%d\n",t->tm_year+1900,t->tm_mon,t->tm_mday);
return 0;
}
这里tt = time(NULL);
就包含了整个内嵌汇编的所有含义,这就是API的作用。API的全称为应用程序编程接口,是一个函数定义,如同这里的time()
,其实libc函数库早已定义好它的系统调用封装例程,所以我们才可以直接拿来用,这就是我们常说的库函数。
了解了完整的系统调用的三层机制和API使用,我们挑选了编号为9的link库函数进行实验。
#include<stdio.h>
#include<unistd.h>
int main(){
int ret;
char * oldpath = "time-asm";
char * newpath = "timetest";
ret = link(oldpath,newpath);
if(ret==0)printf("link successfully");
else printf("Unable to link the file");
return 0;
}
#include<stdio.h>
#include<unistd.h>
int main(){
int ret;
char * oldpath = "time-asm";
char * newpath = "timetest-asm";
asm volatile(
"movl %2, %%ecx\n\t"
"movl %1, %%ebx\n\t"
"movl $0x09, %%eax\n\t"
"int $0x80\n\t"
"movl %%eax, %0"
:"=m"(ret)
:"b" (oldpath),"c"(newpath)
);
if(ret==0)printf("link successfully\n");
else printf("Unable to link the file\n");
return 0;
}
执行过程如图:
这里的link函数和教材中所举的rename函数传参一样,均为两位。这里我简单说明下link函数以及ln命令。
图中为显示的硬链接数目。
#include <unistd.h>
int link (const char * oldpath,const char * newpath);
ln ‘target‘ ‘file‘
命令。symlink函数
头文件
#include <unistd.h>
函数原型
int symlink(const char *oldpath, const char *newpath);
说明
与link函数的返回值一致,可以跨越不同文件系统。
sudo apt install gcc-4.8
ln -s /usr/lib/gcc-4.8 /usr/lib/gccl
2018-2019-1 20189219《Linux内核原理与分析》第五周作业
原文:https://www.cnblogs.com/archemiya/p/9941025.html