Implement system calls

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:

image-20211014192218175

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 namecopy 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 namethe number of characters exceeds 23, return "-1" and set it errnoto 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 namethe user address space pointed to, while ensuring that no out-of name-bounds memory access ( namethe size is sizespecified). The return value is the number of characters copied. If sizeit is less than the required space, return "-1" and set it errnoto 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:

image-20211014163527665

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 calldirectly 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:

  1. Store the number of the system call into the EAX register
  2. Store function parameters in general-purpose registers such as EBX and ECX
  3. Trigger interrupt number 0x80 (int 0x80)
  4. Interrupt Kernel Handling
  5. system call

① explain

  • In the Linux kernel, each system call has a unique system call number. These numbers are defined include/unistd.hstarting 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.cLinux experimental environment main(), it is renamed due to compiler compatibility issues under Windows start()) calls sched_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_gateIt is a macro, defined in include/asm/system.h as:

    #define set_system_gate(n,addr) \
        _set_gate(&idt[n],15,3,addr)
    

    _set_gateThe 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_callfunction address into the interrupt descriptor corresponding to 0x80, that is, after the interrupt 0x80 occurs, the function will be called automatically system_call.

  • System call process: system_callSystem calls are made through functions. This function is purely compiled and defined in kernel/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 .globlvisible 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 about call 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_xxxthe 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.cand study close()the API:

#define __LIBRARY__
#include <unistd.h>
_syscall1(int,close,int,fd)

​ where _syscall1is a macro, include/unistd.hdefined 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_closesaves the macro EAX, fdsaves the parameters EBX, and then makes a 0x80 interrupt call. After the call returns, EAXtake out the return value, store it in , and then determine what return value to pass to the caller of the API __resthrough correct judgment. __reswhere __NR_closeis the number of the system call, include/unistd.hdefined in:

#define __NR_close    6

​ So when adding a system call, you need to modify include/unistd.hthe file to include the newly added one __NR_xxx. In the application program, it is necessary to add the corresponding interface space, that is, _syscallthe 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.hassign corresponding system call numbers to them.

The process is as follows:

cd oslab/linux-0.11/include/unistd.h

image-20211014193225014

vim unistd.h

Please learn how to install and use Vim by yourself.

Add #define __NR_iam 72and#define __NR_whoami 73

image-20211014194405861

2. Add a new system call function in the system call function table

include/linux/sys.hadd in file

cd oslab/linux0.11/include/linux

image-20211014194759491

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.

image-20211014195038924

3. Modify the total number of system calls

Because two new system call functions have been added, kernel/system_call.sthe total number of system calls must be changed to 74

cd oblab/linux0.11/kernel

image-20211014200019858

vim system_call.s

In line 61 modify

image-20211014200205330

4. Implement the system call service routine

kernelCreate 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/Makefilebe 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

image-20211014201250321

6. Compile

linux0.11Compile in the directory

make all

image-20211014201422706

The last line is snyc, both compiled successfully

image-20211014201754072

7. Mount the virtual hard disk

enter oslabdirectory

cd oslab

image-20211014203003403

We mount-hdcmount the virtual hard disk through files, that is, bochsthe files in the virtual machine are all in oslab/hdcthe folder

chmod 777 mount-hdcElevate privileges

./mount-hdcrun the program

image-20211014204456530

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/rootthe directory to write the program

cd hdc/usr/root

image-20211014205308324

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;
}

image-20211014205542213

9. Modify unistd.h and sys.h in the hdc/usr directory

Although we linux0.11modified unistd.hthe sum in the directory sys.h, the sum hdc/usrin the directory has not been modified. Modifications are the same as before.unistd.hsys.h

cd oslab/hdc/usr/include

image-20211014210237647

vim unistd.h

image-20211014194405861

cd oslab/hdc/usr/include/linux

image-20211014210533913

vim sys.h

image-20211014195038924

10. Test procedure

oslabEnter bochsthe virtual machine in the directory

image-20211014210931682

./run

image-20211014211029842

It can be seen that in the current source directory, there are already existing iam.cand whoai.cfiles, just compile them directly.

gcc -o iam iam.c

gcc -o whoami whoami.c

image-20211014211352153

After compiling, remember to enter syncand save the compiled file

run test

./iam fayoung

./whoami

image-20211014211502557

Test success!

Guess you like

Origin blog.csdn.net/fayoung3568/article/details/120773323