前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Linux 内核0.11 系统调用详解(下)

Linux 内核0.11 系统调用详解(下)

作者头像
用户1147447
发布于 2019-05-26 02:00:54
发布于 2019-05-26 02:00:54
3.9K10
代码可运行
举报
文章被收录于专栏:机器学习入门机器学习入门
运行总次数:0
代码可运行

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434858

备注:上讲中,博猪讲到了操作系统是如何让用户程序调用系统函数的,这讲继续接上讲的话题,从一个系统内核系统函数创建的小实验来学习系统内核具体做了些什么。理清下系统调用的整体过程。

实验:在Linux 0.11上添加两个系统调用,并编写两个简单的应用程序测试它们。

  • iam()

第一个系统调用是iam(),其原型为:

int iam(const char * name); 完成的功能是将字符串参数name的内容拷贝到内核中保存下来。要求name的长度不能超过23个字符。返回值是拷贝的字符数。如果name的字符个数超过了23,则返回“-1”,并置errno为EINVAL。在kernal/who.c中实现此系统调用。

  • whoami()

二个系统调用是whoami(),其原型为:

int whoami(char* name, unsigned int size); 它将内核中由iam()保存的名字拷贝到name指向的用户地址空间中,同时确保不会对name越界访存(name的大小由size说明)。返回值是拷贝的字符数。如果size小于需要的空间,则返回“-1”,并置errno为EINVAL。也是在kernal/who.c中实现。

Let‘s go!

等等,linux 0.11内核源码的编写与编译,需要在虚拟机模拟x86环境的情况下进行,这在我的Windows下用Bochs编译运行Linux-0.11有详细阐述,不再赘述。

1、编写内核态下,系统函数具体实现iam()以及whoami()。

目录:/linux/kernel/who.c(创建)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segement.h>
char usnm[64]={0};
int sys_iam(const char* name){
  int result=0;
  int cnt;
  while(get_fs_byte(name+result)!='\0'&&result<64) result++;//统计字符串个数
  if(result>23) return -EINVAL; 
  else{
    for(cnt =0;cnt <=result;cnt++) usnm[cnt]=get_fs_byte(name+cnt); //将字符串写入核心态内存中
    return result;
  }
}
int sys_whoami(char* name,unsigned int size){
  int result=0;
  int cnt;
  while(usnm[result]!='\0'&&result<64) result++;
  if(return >size) return -1;
  else{
    for(cnt =0;cnt<=result;cnt++) put_fs_byte(usnm[cnt],(name+cnt)); 
    return result;
  }
}

2、那操作系统如何调用到who.c中的代码呢?

在上一讲中,我们知道了用户程序将通过int 0x80中断进入核心态,并且,会跳转到system_call函数地址处去执行。接下来我们就来看看system_call源代码。

目录:/linux/kernel/system_call.s(修改),找到nr_system_calls = 86,将系统调用的个数增加两个。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#### int 0x80 --linux 系统调用入口点(调用中断int 0x80,eax 中是调用号).align 2  
_system_call:  
cmpl $nr_system_calls-1,%eax # 调用号如果超出范围的话就在eax 中置-1 并退出。  
ja bad_sys_call  
push %ds # 保存原段寄存器值。  
push %es  
push %fs  
pushl %edx # ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。  
pushl %ecx # push %ebx,%ecx,%edx as parameters  
pushl %ebx # to the system call  
movl $0x10,%edx # set up ds,es to kernel space  
mov %dx,%ds # ds,es 指向内核数据段(全局描述符表中数据段描述符)。  
mov %dx,%es  
movl $0x17,%edx # fs points to local data space  
mov %dx,%fs # fs 指向局部数据段(局部描述符表中数据段描述符)。  
# 下面这句操作数的含义是:调用地址 = _sys_call_table + %eax * 4。参见列表后的说明。  
# 对应的C 程序中的sys_call_table 在include/linux/sys.h 中,其中定义了一个包括72 个  
# 系统调用C 处理函数的地址数组表。  
call _sys_call_table(,%eax,4)  
pushl %eax # 把系统调用号入栈。  
movl _current,%eax # 取当前任务(进程)数据结构地址??eax。  
# 下面97-100 行查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序。  
# 如果该任务在就绪状态但counter[??]值等于0,则也去执行调度程序。  
cmpl $0,state(%eax) # state  
jne reschedule  
cmpl $0,counter(%eax) # counter  
je reschedule  
# 以下这段代码执行从系统调用C 函数返回后,对信号量进行识别处理。  
ret_from_sys_call:  
# 首先判别当前任务是否是初始任务task0,如果是则不必对其进行信号量方面的处理,直接返回。  
# 103 行上的_task 对应C 程序中的task[]数组,直接引用task 相当于引用task[0]。  
movl _current,%eax # task[0] cannot have signals  
cmpl _task,%eax  
je 3f # 向前(forward)跳转到标号3。  
# 通过对原调用程序代码选择符的检查来判断调用程序是否是超级用户。如果是超级用户就直接  
# 退出中断,否则需进行信号量的处理。这里比较选择符是否为普通用户代码段的选择符0x000f  
# (RPL=3,局部表,第1 个段(代码段)),如果不是则跳转退出中断程序。  
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?  
jne 3f  
# 如果原堆栈段选择符不为0x17(也即原堆栈不在用户数据段中),则也退出。  
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?  
jne 3f  
# 下面这段代码(109-120)的用途是首先取当前任务结构中的信号位图(32 位,每位代表1 种信号),  
# 然后用任务结构中的信号阻塞(屏蔽)码,阻塞不允许的信号位,取得数值最小的信号值,再把  
# 原信号位图中该信号对应的位复位(置0),最后将该信号值作为参数之一调用do_signal()。  
# do_signal()在(kernel/signal.c,82)中,其参数包括13 个入栈的信息。  
movl signal(%eax),%ebx # 取信号位图??ebx,每1 位代表1 种信号,共32 个信号。  
movl blocked(%eax),%ecx # 取阻塞(屏蔽)信号位图??ecx。  
notl %ecx # 每位取反。  
andl %ebx,%ecx # 获得许可的信号位图。  
bsfl %ecx,%ecx # 从低位(位0)开始扫描位图,看是否有1 的位,  
# 若有,则ecx 保留该位的偏移值(即第几位0-31)。  
je 3f # 如果没有信号则向前跳转退出。  
btrl %ecx,%ebx # 复位该信号(ebx 含有原signal 位图)。  
movl %ebx,signal(%eax) # 重新保存signal 位图信息??current->signal。  
incl %ecx # 将信号调整为从1 开始的数(1-32)。  
pushl %ecx # 信号值入栈作为调用do_signal 的参数之一。  
call _do_signal # 调用C 函数信号处理程序(kernel/signal.c,82)  
popl %eax # 弹出信号值。  
3: popl %eax  
popl %ebx  
popl %ecx  
popl %edx  
pop %fs  
pop %es  
pop %ds  
iret

且看第20行代码:call _sys_call_table(,%eax,4) ,执行完一系列的保护现场措施后,程序将根据(_sys_call_table的初始地址)+4*%eax 进行跳转执行,%eax就是我们的系统调用中断号。因此,想要实现系统调用,需要在_sys_call_table 下添加iam()和whoami()的函数指针。

3、在sys_call_table中添加iam()和whoami()的函数指针。

目录:/linux/include/linux/sys.h(修改)

添加:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extern int sys_iam();
extern int sys_whoami();

且在sys_call_table中的最后加入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn_ptr sys_call_table={....,....,....,sys_iam,sys_whoami};

刚才谈到系统函数调用中断号%eax,大家一定很含糊这是个什么都东西。先把这个疑问暂存心中,我们继续往下走。

4、假设现在已经把内核态代码编写完成,开始准备用户程序的编写。

目录:/root/iam.c 和/root/whoami.c(创建),注意我引用的头文件哦。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <usname.h>  
int main(int argc,char * argv[]){

  if(argc>1){
    if(iam(argv[1]<0)) return -1;
    else printf("Input ok!");
  }
  else return -1;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <usname.h>
#include <stdio.h>

int main(void){
  char str[128];
  if(whoami(str,24)<0) return -1;
  else printf("%s\n",str);
  return 0;
}

usname.h为我们自定义的系统库函数。

5、创建usname.h系统库函数。

目录:/linux/include/usname.h(创建)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <unistd.h>

_syscall1(int,iam,const char*,name)
_syscall2(int,whoami,char*,name,unsigned int,size)

6、重点来了,继续注意该程序下的头文件,unist.h。

目录:/linux/include/unist.h(修改)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 以下是内核实现的系统调用符号常数,用于作为系统调用函数表中的索引值。( include/linux/sys.h )  
#define __NR_setup 0        /* used only by init, to get system going */  
/* __NR_setup 仅用于初始化,以启动系统 */  
#define __NR_exit 1  
#define __NR_fork 2  
...
#define __NR_iam    87 
#define __NR_whoami 88
...
// 有1 个参数的系统调用宏函数。type name(atype a)  
// %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a)。  
#define _syscall1(type,name,atype,a) /  
type name(atype a) /  
{ /  
long __res; /  
__asm__ volatile ( "int $0x80" /  
: "=a" (__res) /  
: "" (__NR_##name), "b" ((long)(a))); /  
if (__res >= 0) /  
return (type) __res; /  
errno = -__res; /  
return -1; /  
}

第7、8行为我们增加的代码,我还贴出了系统函数调用的宏定义。跟我们自己创建的usname.h一结合,我们就翻译出了如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int iam(char* name)
{
  long __res;
  __asm__ volatile ( "int $0x80" 
  : "=a" (__res)
  : "0" (__NR_iam), "b" ((long)(a)));
  if (__res >= 0)
  return (type) __res; 
  errno = -__res;
  return -1; 
}  

在执行中断调用前,NR_iam的系统调用号传给了eax,由此可见,执行中断后,eax保存的就是系统调用号,而系统调用号配合sys_call_table,最终找到了我们的who.c程序下实现的两个系统函数,故事结束了。。。o(∩_∩)o

7、最后一步,修改makefile文件。重新编译下Linux内核。

目录:/linux/kernel/Makefile(修改)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o
改为:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o who.o

另一处:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
\### Dependencies:exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h
  改为:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
\### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

8、编译用户程序。输出看结果吧,好运!o(∩_∩)o

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年01月31日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
养成个好习惯,for循环,while循环,if,else后面内容该用括号就用括号括起来。。。你的代码我懂,老机器却一直报错。。。
养成个好习惯,for循环,while循环,if,else后面内容该用括号就用括号括起来。。。你的代码我懂,老机器却一直报错。。。
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
linux0.11系统调用过程和fork源码解析
所以执行fork函数就会执行system_call函数,但是在这之前,还有些事情需要做,就是保存现场。下面是操作系统执行系统调用前,在内核栈里保存的寄存器,这个压入的寄存器和iret中断返回指令出栈的寄存器是对应的。其中ip指向的是调用系统调用返回后的下一句代码。
theanarkh
2019/04/24
1.5K0
linux0.11系统调用过程和fork源码解析
linux信号处理源码分析(基于linux0.11)
linux的信号处理时机在系统调用结束后。这里以fork系统调用函数为例子讲解这个过程。下面是fork函数的定义。
theanarkh
2019/09/02
4.7K2
linux信号处理源码分析(基于linux0.11)
系统调用(int 0x80)详解
在系统启动时,会在sched_init(void)函数中调用set_system_gate(0x80,&system_call),设置中断向量号0x80的中断描述符:
全栈程序员站长
2022/09/30
1.6K0
Linux内核之旅/张凯捷——系统调用分析(1)
In computing, a system call is the programmatic way in which a computer program requests a service from the kernel of the operating system it is executed on. This may include hardware-related services (for example, accessing a hard disk drive), creation and execution of new processes, and communication with integral kernel services such as process scheduling. System calls provide an essential interface between a process and the operating system.
Linux阅码场
2019/10/08
1.6K0
Linux内核之旅/张凯捷——系统调用分析(1)
Linux syscall过程分析(万字长文)
为了安全,Linux 中分为用户态和内核态两种运行状态。对于普通进程,平时都是运行在用户态下,仅拥有基本的运行能力。当进行一些敏感操作,比如说要打开文件(open)然后进行写入(write)、分配内存(malloc)时,就会切换到内核态。内核态进行相应的检查,如果通过了,则按照进程的要求执行相应的操作,分配相应的资源。这种机制被称为系统调用,用户态进程发起调用,切换到内核态,内核态完成,返回用户态继续执行,是用户态唯一主动切换到内核态的合法手段(exception 和 interrupt 是被动切换)。
秃头哥编程
2019/08/23
15K1
进程实现原理
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014688145/article/details/50644876
用户1147447
2019/05/26
1.4K0
system_call到iret过程分析
(1)中断有两种,一种是由CPU外部硬件产生的,另一种是由CPU本身执行程序的过程中产生的;外部中断即我们所说的中断(interrupt),外部中断是异步的,由硬件产生,我们无法预测它什么时候发生; (2)x86软件产生的中断是由“INT n”同步产生的,由程序产生,只要CPU执行了一条INT指令,就知道在开始执行下一条指令前就会进入中断服务程序,我们又称此类中断为“陷阱”;int 80为系统调用的陷阱号; (3)异常,是被动的,如页面异常,除数为0的异常; 因此系统调用是中断中的陷阱的一种,系统调用只发生在用户空间,必然会发生用户栈和内核栈的切换。
De4dCr0w
2019/02/27
1.1K0
Linux系统调用原理
系统调用 跟用户自定义函数一样也是一个函数,不同的是 系统调用 运行在内核态,而用户自定义函数运行在用户态。由于某些指令(如设置时钟、关闭/打开中断和I/O操作等)只能运行在内核态,所以操作系统必须提供一种能够进入内核态的方式,系统调用 就是这样的一种机制。
用户7686797
2020/11/12
4.2K0
Linux内核之旅/张凯捷——系统调用分析(2)
在《系统调用分析(1)》Linux内核之旅/张凯捷——系统调用分析(1)中,首先介绍了系统调用的概念,并对早期通过软中断(int 80)来进行系统调用的相关过程进行了分析,最后分析和介绍了为了提高系统调用的响应执行速度的两种机制——vsyscall和vDSO。
Linux阅码场
2019/10/08
2.1K0
Linux内核之旅/张凯捷——系统调用分析(2)
linux系统调用之read源码解析(基于linux0.11)
进程通过系统调用,从而进入中断处理,中断处理从系统调用表里找到sys_read函数执行。
theanarkh
2019/05/14
2.8K0
Win32 Linux汇编语法区别
一、简介 作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但重要性却勿庸置疑,因为它能够完成许多其它语言所无法完成的功能。就拿 Linux 内核来讲,虽然绝大部分代码是用 C 语言编写的,但仍然不可避免地在某些关键地方使用了汇编代码,其中主要是在 Linux 的启动部分。由于这部分代码与硬件的关系非常密切,即使是 C 语言也会有些力不从心,而汇编语言则能够很好扬长避短,最大限度地发挥硬件的性能。
ke1th
2019/05/26
2.4K0
Java离Linux内核有多远?
玩内核的人怎么也懂 Java?这主要得益于我学校的 Java 课程和毕业那会在华为做 Android 手机的经历,几个模块从 APP/Framework/Service/HAL/Driver 扫过一遍,自然对 Java 有所了解。
墨鬓
2020/07/29
1.6K0
Java离Linux内核有多远?
《Linux内核分析》之分析system_call中断处理过程实验总结
先占个位置,在实验楼做实验,刚做完一半忘了延续时间,结果之前写的代码神马的全没了。让我先去角落哭会,总结明天再写。2015-04-04
WindCoder
2018/09/20
1.6K0
《Linux内核分析》之分析system_call中断处理过程实验总结
fork系统调用过程分析
fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。
De4dCr0w
2019/02/27
2.1K0
内核必须懂(一): 用系统调用打印Hello, world!
前言 要自定义系统调用, 常规的两个方法是模块和重编内核, 一起来看看吧. ---- 模块与系统调用 用模块打印Hello, world! 首先看下系统版本和内核版本. 我用的是32位的ubu
sean_yang
2018/10/10
1.3K1
内核必须懂(一): 用系统调用打印Hello, world!
内核必须懂(一): 用系统调用打印Hello, world!
目录 前言 模块与系统调用 用模块打印Hello, world! 用模块添加自定义系统调用 top指令 关闭Linux图形界面 重编内核添加系统调用 解压系统源代码 撰写自定义系统调用 编译内核 测试新内核 最后 ---------- 前言 要自定义系统调用, 常规的两个方法是模块和重编内核, 一起来看看吧. 更新: 在64位ubuntu12.04.5上也成功运行. 解决了14.04, 16.04, 18.04上的问题. ---------- 模块与系统调用 用模块打印Hello, world! 首先看下系
sean_yang
2018/10/01
1.6K0
【linux学习指南】linux捕捉信号(二)软中断&&缺⻚中断?等&& 如何理解内核态和⽤⼾态
如果是这样,操作系统不就可以躺平了吗?对,操作系统⾃⼰不做任何事情,需要什么功能,就向中 断向量表⾥⾯添加⽅法即可.操作系统的本质:就是⼀个死循环!
学习起来吧
2024/12/30
730
【linux学习指南】linux捕捉信号(二)软中断&&缺⻚中断?等&& 如何理解内核态和⽤⼾态
理解协程的实现
glibc提高的功能类似早期setjmp和longjmp。本质上是保存当前的执行上下文到一个变量中,然后去做其他事情。在某个时机再切换回来。从上面函数的名字中,我们大概能知道,这些函数的作用。我们先看一下表示上下文的数据结构(x86架构)。
theanarkh
2020/04/14
8470
理解协程的实现
Linux信号处理
目前 Linux 支持64种信号。信号分为非实时信号(不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。
用户7686797
2020/08/25
5.9K0
分析Linux系统的执行过程
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
matt
2022/10/25
9950
分析Linux系统的执行过程
相关推荐
linux0.11系统调用过程和fork源码解析
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验