Implement system calls
1. Experimental environment
This operation is still based on the experimental environment where the Linux0.11 kernel was compiled last time. The environment is as follows:
2. Experimental objectives
Through the understanding of the above experimental principles, I believe you have a certain understanding of system calls. The goal of this experiment is to add two system calls to Linux 0.11 and write two simple applications to test them. The first system call is iam()
, whose prototype is:
int iam(const char * name);
The completed function is to name
copy the content of the string parameter to the kernel and save it. It is required that the length of name cannot exceed 23 characters. The return value is the number of characters copied. If name
the number of characters exceeds 23, return "-1" and set it errno
to EINVAL
.
The second system call is whoami()
, whose prototype is:
int whoami(char* name, unsigned int size);
iam()
It copies the saved name in the kernel to name
the user address space pointed to, while ensuring that no out-of name
-bounds memory access ( name
the size is size
specified). The return value is the number of characters copied. If size
it is less than the required space, return "-1" and set it errno
to EINVAL
.
Implement the above two system calls in kernal/who.c.
3. Experimental principle
1. System call (system call)
Through the study of operating system courses, we know that the process space of the operating system can be divided into user space and kernel space , which require different execution permissions. The system calls run in kernel space. In a computer, a system call refers to a program running in user space requesting a service that requires higher privileges to run from the operating system kernel . System calls provide the interface between user programs and the operating system . Most system interactive operations require running in kernel mode. Such as device IO operations or inter-process communication.
In layman's terms, system calls are calls to various libraries when we write C language code, and calls to various packages when we write JAVA code. It's just that the code of the system call is provided by the operating system kernel and runs in the kernel core state, while the common library function and package call is provided by the function library or the user itself and runs in the user state.
The relationship between applications, library functions and kernel system calls in Linux0.11 is shown in the following figure:
2. The implementation process of the system call
Under normal circumstances, there is no difference in code between calling a system call and calling an ordinary custom function, but what happens after the call is very different. Calling a custom function is to call
directly jump to the address of the function through instructions and continue to run. The call system call is to call an interface function written for the system call in the system library, called API (Application Programming Interface). The API cannot complete the real function of the system call. What it needs to do is to call the real system call. The process is:
- Store the number of the system call into the EAX register
- Store function parameters in general-purpose registers such as EBX and ECX
- Trigger interrupt number 0x80 (int 0x80)
- Interrupt Kernel Handling
- system call
① explain
-
In the Linux kernel, each system call has a unique system call number. These numbers are defined
include/unistd.h
starting at line 60 in the file. These system call numbers actually correspond to the index valuesinclude/linux/sys.h
of entries in the system call handler pointer array table defined in sys_call_table[] . The kernel starts the corresponding system call through the index value -
EAX, EBX, ECX... These are general-purpose registers: the extension registers of AX, BX, and CX , and the E in front is extend. Registers like AX are 16 bits, while registers like EAX are 32 bits.
-
0x80 Interrupt: In the course of computer composition principle and operating system, we know that interrupt refers to the corresponding hardware / software processing. System call interrupts are the only interface for user programs to use operating system resources . Its interrupt number in the interrupt vector table is int 128 (ie 0x80).
-
Interrupt kernel processing: When the kernel is initialized, the main function (in the
init/main.c
Linux experimental environmentmain()
, it is renamed due to compiler compatibility issues under Windowsstart()
) callssched_init()
the initialization function:void main(void) { …… time_init(); sched_init(); buffer_init(buffer_memory_end); …… }
sched_init()
Defined in kernel/sched.c as:void sched_init(void) { …… set_system_gate(0x80,&system_call); }
set_system_gate
It is a macro, defined in include/asm/system.h as:#define set_system_gate(n,addr) \ _set_gate(&idt[n],15,3,addr)
_set_gate
The definition is:#define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \ "movw %0,%%dx\n\t" \ "movl %%eax,%1\n\t" \ "movl %%edx,%2" \ : \ : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ "d" ((char *) (addr)),"a" (0x00080000))
Although it looks cumbersome, it is actually very simple, just fill in the IDT (Interrupt Descriptor Table), write the
system_call
function address into the interrupt descriptor corresponding to 0x80, that is, after the interrupt 0x80 occurs, the function will be called automaticallysystem_call
. -
System call process:
system_call
System calls are made through functions. This function is purely compiled and defined inkernel/system_call.s
:…… nr_system_calls = 72 # 这是系统调用总数。如果增删了系统调用,必须做相应修改 …… .globl system_call .align 2 system_call: cmpl $nr_system_calls-1,%eax # 检查系统调用编号是否在合法范围内 ja bad_sys_call push %ds push %es push %fs pushl %edx pushl %ecx pushl %ebx # push %ebx,%ecx,%edx,是传递给系统调用的参数 movl $0x10,%edx # 让ds,es指向GDT,内核地址空间 mov %dx,%ds mov %dx,%es movl $0x17,%edx # 让fs指向LDT,用户地址空间 mov %dx,%fs call sys_call_table(,%eax,4) pushl %eax movl current,%eax cmpl $0,state(%eax) jne reschedule cmpl $0,counter(%eax) je reschedule
system_call
Make.globl
visible to other functions with modifiers. In the Windows experimental environment, you will see that it has an underscore prefix, which is determined by the characteristics of different versions of the compiler, and there is no substantial difference.call sys_call_table(,%eax,4)
Before it was some stack protection, modifying the segment selector to the kernel segment,call sys_call_table(,%eax,4)
and then checking whether it needs to be rescheduled. These are not directly related to this experiment, and we only care aboutcall sys_call_table(,%eax,4)
this sentence here. According to the assembly addressing method it is actually:call sys_call_table + 4 * %eax # 其中eax中放的是系统调用号,即__NR_xxxxxx
Obviously, sys_call_table must be the starting address of an array of function pointers, which is defined in
include/linux/sys.h
:fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,……
To add a new system call, you need to add its corresponding function reference in this function table— of course, the position of
sys_xxx
the function in the array must correspond to the value of .sys_call_table
__NR_xxxxxx
②Case
There are some implemented APIs in the lib directory of Linux0.11. The reason Linus wrote them is that after the kernel is loaded, it will switch to user mode, do some initialization work, and then start the shell. And a lot of work in user mode needs to rely on some system calls to complete, so the APIs of these system calls are implemented in the kernel. Let's take a look lib/close.c
and study close()
the API:
#define __LIBRARY__
#include <unistd.h>
_syscall1(int,close,int,fd)
where _syscall1
is a macro, include/unistd.h
defined in . _syscall1(int,close,int,fd)
Macro expansion will be performed to get :
int close(int fd)
{
long __res;
__asm__ volatile (
"int $0x80"
: "=a" (__res)
: "0" (__NR_close),"b" ((long)(fd)));
if (__res >= 0)
return (int) __res;
errno = -__res;
return -1;
}
This is the definition of API. It first __NR_close
saves the macro EAX
, fd
saves the parameters EBX
, and then makes a 0x80 interrupt call. After the call returns, EAX
take out the return value, store it in , and then determine what return value to pass to the caller of the API __res
through correct judgment. __res
where __NR_close
is the number of the system call, include/unistd.h
defined in:
#define __NR_close 6
So when adding a system call, you need to modify include/unistd.h
the file to include the newly added one __NR_xxx
. In the application program, it is necessary to add the corresponding interface space, that is, _syscall
the function. As follows:
#define __LIBRARY__ /* 有它,_syscall1等才有效。详见unistd.h */
#include <unistd.h> /* 有它,编译器才能获知自定义的系统调用的编号 */
_syscall0(type,name);
_syscall1(type,name,type1,arg1);
_syscall2(type,name,type1,arg1,type2,arg2);
/*
其中,type表示所生成系统调用的返回值类型,name表示该系统调用的名称,typeN、argN分别表示第N个参数的类型和名称,它们的数目和_syscall后面的数字一样大。
这些宏的作用是创建名为name的函数,_syscall后面跟的数字指明了该函数的参数的个数。
*/
4. Experimental process
1. Add system call number
According to the content of the experimental principle, now to add two new system call programs, it is necessary to unistd.h
assign corresponding system call numbers to them.
The process is as follows:
cd oslab/linux-0.11/include/unistd.h
vim unistd.h
Please learn how to install and use Vim by yourself.
Add #define __NR_iam 72
and#define __NR_whoami 73
2. Add a new system call function in the system call function table
include/linux/sys.h
add in file
cd oslab/linux0.11/include/linux
vim sys.h
Starting at line 73, add extern int sys_aim();
and extern int sys_whoami();
.
Add at the end of the parentheses on the last line ,sys_iam,sys_whoami
.
Note: The order of addition should be the same, otherwise a segmentation fault error will be reported when the program is running at the end, and the running program cannot be found.
3. Modify the total number of system calls
Because two new system call functions have been added, kernel/system_call.s
the total number of system calls must be changed to 74
cd oblab/linux0.11/kernel
vim system_call.s
In line 61 modify
4. Implement the system call service routine
kernel
Create files in the directory who.c
.
vim who.c
#include <unistd.h>
#include <errno.h>
#include <linux/kernel.h>
#include <asm/segment.h>
static char str[24];
static unsigned long len;
int sys_iam(const char* name) {
int i;
char tmp[24];
for(i = 0; i < 24; i++) {
tmp[i] = get_fs_byte(name + i);
if(tmp[i] == 0) break;
}
if(i == 24) {
return -(EINVAL);
}
// clear str
for(i = 0; i < 24; i++) {
str[i] = 0;
}
// copy
for(i = 0; tmp[i] != 0; i++) {
str[i] = tmp[i];
}
len = i;
return len;
}
int sys_whoami(char* name, unsigned int size) {
int i;
if(size < len) {
return -(EINVAL);
}
for(i = 0; i < size && str[i] != 0; i++) {
put_fs_byte(str[i], name + i);
}
put_fs_byte(0, name + i);
return len;
}
5. Modify the Makefile
After the file is written who.c
, it must be compiled and written into the system kernel. So the compilation information to kernel/Makefile
be added in the file .who.c
Two additions are required. One is:
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
Change to:
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
Another place:
### 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
Change to:
### 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
vim Makefile
6. Compile
linux0.11
Compile in the directory
make all
The last line is snyc
, both compiled successfully
7. Mount the virtual hard disk
enter oslab
directory
cd oslab
We mount-hdc
mount the virtual hard disk through files, that is, bochs
the files in the virtual machine are all in oslab/hdc
the folder
chmod 777 mount-hdc
Elevate privileges
./mount-hdc
run the program
You can see that the hard disk icon appears on the side, indicating that the mount is successful.
8. Write iam.c and whoami.c
Enter into urs/root
the directory to write the program
cd hdc/usr/root
vim iam.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall1(int, iam, char*, name)
int main(int argc, char* argv[]) {
if(argc <= 1){
printf("input error\n");
return -1;
}
iam(argv[1]);
return 0;
}
vim whoami.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall2(int, whoami, char*, name, unsigned int, size)
char name[25] = {
};
int main(int argc, char* argv[]) {
whoami(name, 25);
printf("%s\n", name);
return 0;
}
9. Modify unistd.h and sys.h in the hdc/usr directory
Although we linux0.11
modified unistd.h
the sum in the directory sys.h
, the sum hdc/usr
in the directory has not been modified. Modifications are the same as before.unistd.h
sys.h
cd oslab/hdc/usr/include
vim unistd.h
cd oslab/hdc/usr/include/linux
vim sys.h
10. Test procedure
oslab
Enter bochs
the virtual machine in the directory
./run
It can be seen that in the current source directory, there are already existing iam.c
and whoai.c
files, just compile them directly.
gcc -o iam iam.c
gcc -o whoami whoami.c
After compiling, remember to enter sync
and save the compiled file
run test
./iam fayoung
./whoami
Test success!