I'm developing a simple OS kernel. Now, I want my kernel can "load and execute" my userspace programs.
Github: https://github.com/stskyblade/StarOS
At branch: dev-process
I have a userspace programs "testadd". (https://github.com/stskyblade/StarOS/bl ... estadd.cpp)
The code loads userspace program:
https://github.com/stskyblade/StarOS/bl ... ss.cpp#L10
What I want:
I want my kernel to execute program "testadd". And I can observe the execution of "testadd" by GDB debugging, like EIP and general registers changing.
What I encounter:
It triggers a Interrupt 9(Coprocessor Segment Overrun) when I use an "iret" instruction to switch control flow to userspace programs. I think this is because I didn't prepare the environment of userspace program right.
Simplely describe the logic in function "execv":
- read the executable file to memory in kernel
- prepare a new paging directory for the program
- parse ELF structure of the file: add memory mappings, copy .text section
- prepare process LDT, one code segment, one data segment
- set a TSS(Task State Segment), and set fields
- add a TSS(Task State Segment) descriptor in GDT
- set NT(nested task) bit
- to simulate the stack content when a userspace program is interrupted, I push SS, ESP, EFLAGS, CS, EIP to the kernel stack
- use IRET to return to the process, https://github.com/stskyblade/StarOS/bl ... s.cpp#L233
- IRET triggers Interrupt 9 immediately
Background:
My kernel executes in Protected and Paging-enabled environment in ring 0 I think.
I want my userspace program to run in ring 3.
Memory mapping in kernel:
Segment selectors all points to 0x0, so virtual address is identical to linear address.
Only used memory pages is mapped to the same physical address in kernel page directory, so linear address is same as physical address.
Memory mapping in process space:
Only .text section is mapped to the virtual address according to the ELF file. Other address space is not been used yet.
[Closed]How to prepare and enter the first user program?
-
- Posts: 12
- Joined: Sun Jul 28, 2024 9:56 pm
[Closed]How to prepare and enter the first user program?
Last edited by stskyblade on Thu Dec 19, 2024 5:27 am, edited 1 time in total.
-
- Member
- Posts: 797
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: How to prepare and enter the first user program?
I can't build because of a missing src/vectors.pl file. But looking at your code I have to ask if there is a reason you are using hardware task switching through task gates rather than rolling your own task switching? Can you fix you project so it can be cloned and others can build it?
With that being said your use of inline assembly could be a problem. An example: https://github.com/stskyblade/StarOS/bl ... s.cpp#L221
In 32-bit code can't reliably do a `push` alone by itself in inline assembly like this. The compiler will be unaware that the stack layout has changed and it could alter the behaviour of the code in ways you don't expect. Have no idea if this is even related to your issue.
With that being said your use of inline assembly could be a problem. An example: https://github.com/stskyblade/StarOS/bl ... s.cpp#L221
Code: Select all
uint32_t data = 0;
data = tss->ss;
uint32_t data = 0;
data = tss->ss;
__asm__ __volatile__("push %0\n\t"::"r"(data));
data = tss->esp;
__asm__ __volatile__("push %0\n\t"::"r"(data));
data = tss->eflags;
__asm__ __volatile__("push %0\n\t"::"r"(data));
data = tss->cs;
__asm__ __volatile__("push %0\n\t"::"r"(data));
data = tss->eip;
__asm__ __volatile__("push %0\n\t"::"r"(data));
KERNEL_TSS->back_link = selector; // FIXME: bug
__asm__ __volatile__("debug_process:\n\t" ::);
__asm__ __volatile__("iret\n\t" ::);
-
- Posts: 12
- Joined: Sun Jul 28, 2024 9:56 pm
Re: How to prepare and enter the first user program?
Be very very careful: when build the disk image, it needs accessing to loop device. Don't let it destroy you computer.
I have updated my Readme.md file, with some building instructions. I now use CMake build system. Forget about the Makefile.
I don't use src/vectors.pl. I rewrite it with python, in file `codegen_interrupt_handlers.py`. Code generation is done in CMakeLists.txt.
Why do I choose hardware task switching method?
Because I didn't add memory mappings for kernel code in process address space. I need to find a way to change CS:EIP and PDBR(paging directory, CR3) simultaneously, as well as change privilege level to ring 3. I don't know how to do it.
I read INTEL 80386 PROGRAMMER'S REFERENCE MANUAL, and think this is the only way to do it. Tell me if I'm wrong.
I'm not an expertise.
Code: Select all
97 # make a disk image
98 # partition 200MB
99 # have two text file
100 set(USB_DEVICE /dev/sdb)
101 set(MOUNT_POINT /mnt/fat32_device)
102 set(FS_IMG /tmp/fs.img)
103 set(LOOP_DEVICE /dev/loop0)
104 set(USER_PROGRAMS testadd)
105 add_custom_command(
106 OUTPUT ${PROJECT_BINARY_DIR}/fs.img
107 DEPENDS ${KERNEL_FILE} ${USER_PROGRAMS}
108 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
109
110 COMMAND sudo dd if=/dev/zero of=${FS_IMG} count=512000
111 COMMAND sudo losetup ${LOOP_DEVICE} ${FS_IMG}
112 COMMAND echo "Partition..."
113 COMMAND sudo sfdisk -q ${LOOP_DEVICE} < ${PROJECT_SOURCE_DIR}/partition_layout.dump
114 COMMAND sudo partprobe ${LOOP_DEVICE}
115 COMMAND sudo mkfs.fat -F 32 ${LOOP_DEVICE}p1
116 COMMAND sudo mkdir -p ${MOUNT_POINT}
117 COMMAND sudo mount ${LOOP_DEVICE}p1 ${MOUNT_POINT}
118 COMMAND echo "Copy files..."
119 COMMAND sudo cp ${PROJECT_BINARY_DIR}/${KERNEL_FILE} ${MOUNT_POINT}
120 COMMAND sudo cp ${PROJECT_BINARY_DIR}/${USER_PROGRAMS} ${MOUNT_POINT}
121 COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/hello.txt ${MOUNT_POINT}
122 COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/world.txt ${MOUNT_POINT}
123 COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/large_hello.txt ${MOUNT_POINT}
124 COMMAND sudo umount ${LOOP_DEVICE}p1
125 COMMAND sudo rm -rf ${MOUNT_POINT}
126 COMMAND sudo losetup -d ${LOOP_DEVICE}
127 # directly manipulate image on project directory is extreamly slow
128 COMMAND sudo cp /tmp/fs.img ${PROJECT_BINARY_DIR}/fs.img
129 COMMAND echo "Done."
130 )
Code: Select all
git clone -b dev-process https://github.com/stskyblade/StarOS.git
cd StarOS
mkdir build
cd build
cmake ..
cmake --build . --target qemu
Why do I choose hardware task switching method?
Because I didn't add memory mappings for kernel code in process address space. I need to find a way to change CS:EIP and PDBR(paging directory, CR3) simultaneously, as well as change privilege level to ring 3. I don't know how to do it.
I read INTEL 80386 PROGRAMMER'S REFERENCE MANUAL, and think this is the only way to do it. Tell me if I'm wrong.
I'm not an expertise.
-
- Member
- Posts: 5568
- Joined: Mon Mar 25, 2013 7:01 pm
Re: How to prepare and enter the first user program?
Unless you're running your OS on a 386 with a coprocessor installed, you can't have a coprocessor segment overrun. It sounds like you enabled maskable interrupts without initializing the interrupt controllers and received IRQ1.
Do you really need a LDT? Why can't you use the GDT?stskyblade wrote: ↑Wed Dec 18, 2024 5:31 am- prepare process LDT, one code segment, one data segment
Hardware task switching is almost as obsolete as the coprocessor segment overrun exception. It's slow, it doesn't work in 64-bit mode, and it doesn't exist in non-x86 architectures. If you want to change CR3 every time you switch between kernel mode and user mode, it's better to always map a small piece of kernel code.
Re: How to prepare and enter the first user program?
Honestly, hardware task switching was always too complicated for me to figure out. It only saves the general purpose registers, which I can do in software anyway, and if I do it in software, then I can save the FPU registers (and other extended registers) at the same time, which avoids problems on multi-CPU systems. This way I can migrate any task to another CPU as I see fit.stskyblade wrote: ↑Wed Dec 18, 2024 6:07 pm Why do I choose hardware task switching method?
Because I didn't add memory mappings for kernel code in process address space. I need to find a way to change CS:EIP and PDBR(paging directory, CR3) simultaneously, as well as change privilege level to ring 3. I don't know how to do it.
I read INTEL 80386 PROGRAMMER'S REFERENCE MANUAL, and think this is the only way to do it. Tell me if I'm wrong.
I would not use the 80386 programmer's manual. Things have changed in the last 30 years. Go and download the current Intel SDM or AMD APM. They contain the same information, it's just a green-vs-blue thing, mostly. In particular the initialization of paging has changed and no longer works the way it was described in that manual (which is also why SCO Unix for the 386 doesn't work on a 486 or higher CPU). The normal way to change address spaces is to do it in a place that is mapped to the same physical address in both the old and new address spaces. This is one of the reasons why all modern operating systems map the kernel into all address spaces. Once you have one place that stays the same when a new CR3 is loaded, it is no longer necessary to change CR3 and CS:EIP at the same time, and the need for hardware task switching (an arcane mechanism I wouldn't trust any emulator to implement right) just falls away.
Carpe diem!
-
- Posts: 12
- Joined: Sun Jul 28, 2024 9:56 pm
Re: How to prepare and enter the first user program?
According to your suggestions, I realized that using hardware context switching is not a good choice. I decided to use the software context switching method. Of course, I need to map kernel code memory in every process's address space.
In this case, this problem no longer exists. I will close this post.
I sincerely thank you for your help. This is a really difficult question for me, and it has stuck me for a long time.
In this case, this problem no longer exists. I will close this post.
I sincerely thank you for your help. This is a really difficult question for me, and it has stuck me for a long time.