Update markdown and tabs and stuff

This commit is contained in:
Thomas Lovén 2016-10-22 17:07:50 +02:00
parent 67e817490e
commit a27daafa0a
39 changed files with 2249 additions and 2139 deletions

View File

@ -11,155 +11,152 @@ personal\_letter.
The class looks as follows:
:::latex
%
% personal_letter
%
% Created by Thomas Loven on 2010-02-24. Feel free to use.
%
%
% personal_letter
%
% Created by Thomas Loven on 2010-02-24. Feel free to use.
%
\ProvidesClass{personal_letter}
% Based on article
\LoadClass[a4paper, oneside]{article}
\usepackage{ifthen}
\ProvidesClass{personal_letter}
% Define functions for user changeable variables
\makeatletter
% Based on article
\LoadClass[a4paper, oneside]{article}
\newcommand{\adress}[1]{\def \@adress{#1}}
\newcommand{\telephone}[1]{\def \@telephone{#1}}
\newcommand{\email}[1]{\def \@email{#1}}
\newcommand{\name}[1]{\def \@name{#1}}
\newcommand{\place}[1]{\def \@place{#1}}
%\newcommand{\date}[1]{\def \@date{#1}}
\newcommand{\greeting}[1]{\def \@greeting{#1}}
\newcommand{\closing}[1]{\def \@closing{#1}}
\newcommand{\url}[1]{\def \@url{#1}}
\adress{}
\telephone{}
\email{}
\name{}
\place{}
\date{}
\greeting{}
\closing{}
\url{}
\makeatother
% Include usefull packages
\usepackage[utf8]{inputenc}
\usepackage{fullpage}
\usepackage{fancyhdr}
\usepackage{setspace}
\usepackage[swedish]{babel}
\usepackage{longtable}
\usepackage{ifthen}
% Make graphics work with pdf or dvi files
\usepackage{ifpdf}
\ifpdf
\usepackage[pdftex]{graphicx}
\DeclareGraphicsExtensions{.pdf, .jpg, .tif}
\else
\usepackage{graphicx}
\DeclareGraphicsExtensions{.eps, .jpg}
\fi
% Costruct heading
\makeatletter
\fancyfoot[LO, LE]{\@adress}
\fancyfoot[RO, RE]{ \ifthenelse{\equal{\@telephone}{}}{}{ \@telephone \\ \@email \\ \@url }}
\fancyfoot[C]{}
\setlength{\headheight}{0 cm}
\setlength{\headsep}{0 cm}
\renewcommand{\headrulewidth}{0pt}
\addtolength{\textheight}{0 cm}
\renewcommand{\footrulewidth}{0.5pt}
\pagestyle{fancy}
\makeatletter
%Body of the letter
\makeatletter
\newenvironment{body}% {
\begin{quotation}
\begin{onehalfspace}
\setlength{\parskip}{1ex}
\ifthenelse{\equal{\@place}{}}{}{ \begin{flushright} \@place \\ \@date \end{flushright} }
\vspace{2ex}
\bf
\noindent
\@greeting
\rm
}%
{
\vspace{10pt}
\ifthenelse{\equal{\@closing}{}}{}{ \noindent \@closing \\ \noindent \@name } \end{onehalfspace} \end{quotation}
}
\makeatother
\newcommand{\titel}[1]{ \begin{centering} {\Huge #1} \end{centering} }
\newcommand{\rubrik}[1]{ \vspace{1.5em}
{\large \bf #1}
}
{: .prettyprint .lang-tex}
% Define functions for user changeable variables
\makeatletter
\newcommand{\adress}[1]{\def \@adress{#1}}
\newcommand{\telephone}[1]{\def \@telephone{#1}}
\newcommand{\email}[1]{\def \@email{#1}}
\newcommand{\name}[1]{\def \@name{#1}}
\newcommand{\place}[1]{\def \@place{#1}}
%\newcommand{\date}[1]{\def \@date{#1}}
\newcommand{\greeting}[1]{\def \@greeting{#1}}
\newcommand{\closing}[1]{\def \@closing{#1}}
\newcommand{\url}[1]{\def \@url{#1}}
\adress{}
\telephone{}
\email{}
\name{}
\place{}
\date{}
\greeting{}
\closing{}
\url{}
\makeatother
% Include usefull packages
\usepackage[utf8]{inputenc}
\usepackage{fullpage}
\usepackage{fancyhdr}
\usepackage{setspace}
\usepackage[swedish]{babel}
\usepackage{longtable}
% Make graphics work with pdf or dvi files
\usepackage{ifpdf}
\ifpdf
\usepackage[pdftex]{graphicx}
\DeclareGraphicsExtensions{.pdf, .jpg, .tif}
\else
\usepackage{graphicx}
\DeclareGraphicsExtensions{.eps, .jpg}
\fi
% Costruct heading
\makeatletter
\fancyfoot[LO, LE]{\@adress}
\fancyfoot[RO, RE]{ \ifthenelse{\equal{\@telephone}{}}{}{ \@telephone \\ \@email \\ \@url }}
\fancyfoot[C]{}
\setlength{\headheight}{0 cm}
\setlength{\headsep}{0 cm}
\renewcommand{\headrulewidth}{0pt}
\addtolength{\textheight}{0 cm}
\renewcommand{\footrulewidth}{0.5pt}
\pagestyle{fancy}
\makeatletter
%Body of the letter
\makeatletter
\newenvironment{body}% {
\begin{quotation}
\begin{onehalfspace}
\setlength{\parskip}{1ex}
\ifthenelse{\equal{\@place}{}}{}{ \begin{flushright} \@place \\ \@date \end{flushright} }
\vspace{2ex}
\bf
\noindent
\@greeting
\rm
}%
{
\vspace{10pt}
\ifthenelse{\equal{\@closing}{}}{}{ \noindent \@closing \\ \noindent \@name } \end{onehalfspace} \end{quotation}
}
\makeatother
\newcommand{\titel}[1]{ \begin{centering} {\Huge #1} \end{centering} }
\newcommand{\rubrik}[1]{ \vspace{1.5em}
{\large \bf #1}
}
Plain and simple.
And a usage example:
\documentclass{personal_letter}
\name{Thomas Lovén}
\place{Göteborg}
\date{6 april 2010}
\adress{Thomas Lovén \\ Xxxxxxxxxxxxxxx XX \\ XXX XX Xxxxxx Xxxxxxxx}
\telephone{+XX XX XXX XX XX}
\email{thomasloven@gmail.com}
\url{thomasloven.wordpress.com}
\begin{document}
\greeting{Salvete!}
\closing{Di vos incolumes custodiant.}
\begin{body}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
non arcu non massa accumsan tincidunt. Suspendisse non est quis
massa sollicitudin faucibus. Quisque gravida vulputate nisi pharetra
ultrices. Fusce tincidunt ante quis lacus adipiscing eget dictum
justo luctus. Vivamus nec tempus diam. Vivamus rhoncus varius arcu,
et vulputate purus aliquam eget. Cras eget suscipit lectus. Donec
nec nulla ac urna ultricies bibendum sed vitae nibh. Suspendisse
consectetur luctus quam eget vulputate. Pellentesque et nisl et quam
vestibulum auctor quis et metus. Sed cursus tellus at felis lobortis
ut tincidunt purus porttitor.
:::latex
\documentclass{personal_letter}
Donec gravida metus eu dui rutrum nec bibendum libero molestie.
Aenean et odio massa. Donec pulvinar augue non tellus vulputate nec
congue justo accumsan. Nam pretium sagittis dictum. Sed semper auctor
neque in commodo. Mauris dignissim ante ac nibh pretium consequat.
Donec orci tortor, pharetra non congue vel, ultrices sit amet lacus.
Suspendisse a lacus nec ante venenatis bibendum vitae id dui. Nam
semper arcu facilisis nunc euismod volutpat. Donec accumsan velit nec
ante lacinia pulvinar. Phasellus ut varius enim. Pellentesque vel
augue odio. Suspendisse sed nisi vel magna euismod semper. Maecenas
erat neque, tristique id consequat id, mollis eu enim. Phasellus
laoreet pulvinar ante accumsan posuere. Proin viverra dui id ipsum
hendrerit non mollis mi rutrum.
\end{body}
\end{document}
{: .prettyprint .lang-tex}
\place{Göteborg}
\adress{Thomas Lovén \\ Xxxxxxxxxxxxxxx XX \\ XXX XX Xxxxxx Xxxxxxxx}
\telephone{+XX XX XXX XX XX}
\email{thomasloven@gmail.com}
\url{thomasloven.wordpress.com}
\begin{document}
\greeting{Salvete!}
\closing{Di vos incolumes custodiant.}
\begin{body}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
non arcu non massa accumsan tincidunt. Suspendisse non est quis
massa sollicitudin faucibus. Quisque gravida vulputate nisi pharetra
ultrices. Fusce tincidunt ante quis lacus adipiscing eget dictum
justo luctus. Vivamus nec tempus diam. Vivamus rhoncus varius arcu,
et vulputate purus aliquam eget. Cras eget suscipit lectus. Donec
nec nulla ac urna ultricies bibendum sed vitae nibh. Suspendisse
consectetur luctus quam eget vulputate. Pellentesque et nisl et quam
vestibulum auctor quis et metus. Sed cursus tellus at felis lobortis
ut tincidunt purus porttitor.
Donec gravida metus eu dui rutrum nec bibendum libero molestie.
Aenean et odio massa. Donec pulvinar augue non tellus vulputate nec
congue justo accumsan. Nam pretium sagittis dictum. Sed semper auctor
neque in commodo. Mauris dignissim ante ac nibh pretium consequat.
Donec orci tortor, pharetra non congue vel, ultrices sit amet lacus.
Suspendisse a lacus nec ante venenatis bibendum vitae id dui. Nam
semper arcu facilisis nunc euismod volutpat. Donec accumsan velit nec
ante lacinia pulvinar. Phasellus ut varius enim. Pellentesque vel
augue odio. Suspendisse sed nisi vel magna euismod semper. Maecenas
erat neque, tristique id consequat id, mollis eu enim. Phasellus
laoreet pulvinar ante accumsan posuere. Proin viverra dui id ipsum
hendrerit non mollis mi rutrum.
\end{body}
\end{document}
![Sample](/media/img/provbrev.jpg)

View File

@ -11,32 +11,30 @@ option enabled and one that runs entirely in a command line environment.
Let's go!
curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz
tar -zxvf bochs-2.5.1.tar.gz
mkdir build-bochs
cd build-bochs
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --with-term
make all
make install
{: .prettyprint .lan-sh}
$ curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz
$ tar -zxvf bochs-2.5.1.tar.gz
$ mkdir build-bochs
$ cd build-bochs
$ ../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --with-term
$ make all
$ make install
And that's the terminal version. ### Important note This configure line won't
actually work. I'll update with the new one as soon as I can.
And that's the terminal version.
### Important note
This configure line won't actually work. I'll update with the new one as soon as
I can.
Now we move this to *bochs-term* and compile the version with the debugger.
mv /usr/local/bin/bochs /usr/local/bin/bochs-term
rm *
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --enable-debugger --enable-disasm --with-x11
make all
make install
{: .prettyprint .lan-sh}
$ mv /usr/local/bin/bochs /usr/local/bin/bochs-term
$ rm *
$ ../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --enable-debugger --enable-disasm --with-x11
$ make all
$ make install
Finally, copy the bochs bioses to a public place
cp -r ../bochs-2.5.1/bios /usr/share/bochs
{: .prettyprint .lan-sh}
$ cp -r ../bochs-2.5.1/bios /usr/share/bochs
And that's it.

View File

@ -21,18 +21,17 @@ c-compiler. An update was required on one of my computers.
Compiling gcc also requires the mpfr package to be installed. This I did with
[Homebrew](http://mxcl.github.com/homebrew/).
brew install mpfr
{: .prettyprint}
:::bash
$ brew install mpfr
I downloaded all the sources I needed from [gnu.org](http://gnu.org).
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.gz
curl -O http://ftp.gnu.org/gnu/gcc/gcc-4.6.3/gcc-core-4.6.3.tar.gz
curl -O http://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.tar.gz
curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
{: .prettyprint .lang-bash}
:::bash
$ curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.gz
$ curl -O http://ftp.gnu.org/gnu/gcc/gcc-4.6.3/gcc-core-4.6.3.tar.gz
$ curl -O http://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
$ curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.tar.gz
$ curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
Feel free to use later versions, but if you do, I cannot guarantee that the
code posted in my logs will work for you (it's very likely to work, but not
@ -40,33 +39,32 @@ code posted in my logs will work for you (it's very likely to work, but not
gcc, so after extracting all archives, they are simply copied into the gcc
source
mv gmp-5.0.2 gcc-4.6.3/gmp
mv mpfr-3.1.0 gcc-4.6.3/mpfr
mv mpc-0.9 gcc-4.6.3/mpc
{: .prettyprint .lang-sh}
:::bash
$ mv gmp-5.0.2 gcc-4.6.3/gmp
$ mv mpfr-3.1.0 gcc-4.6.3/mpfr
$ mv mpc-0.9 gcc-4.6.3/mpc
In order not to mess up the source, binutils and gcc were built out of tree.
mkdir build-binutils
cd build-binutils
export PREFIX=/usr/local/cross
export TARGET=i386-elf
../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
make all
make install
{: .prettyprint .lang-bash}
:::bash
$ mkdir build-binutils
$ cd build-binutils
$ export PREFIX=/usr/local/cross
$ export TARGET=i386-elf
$ ../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
$ make all
$ make install
And the same for gcc, using the new binutils
cd ..
mkdir build-gcc
cd build-gcc
export PATH=$PATH:$PREFIX/bin
../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
make all-gcc
make install-gcc
{: .prettyprint .lang-bash}
:::bash
$ cd ..
$ mkdir build-gcc
$ cd build-gcc
$ export PATH=$PATH:$PREFIX/bin
$ ../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
$ make all-gcc
$ make install-gcc
It's really important to run _make all-gcc_ and _make install-gcc_ and __not__
_make all_ and _make install_ here. It probably works anyway - if you ever

View File

@ -12,11 +12,11 @@ First point today was making a directory structure and creating a [git
repository](http://github.com/thomasloven/os5). The directory structure starts
off as follows
os5/
|-- build/
|-- include/
`-- kernel/
`-- include/
os5/
|-- build/
|-- include/
`-- kernel/
`-- include/
A difference from previous times is that the _include_ directory is outside the
kernel directory. That's because last time I realized I had a lot of include
@ -24,12 +24,12 @@ files that were used both in the kernel and many other parts. I'll probably
move it into some library directory later. Know what? Let's move it into
a library directory right now...
os5/
|-- build/
|-- kernel/
| `-- include/
`-- library/
`-- include/
os5/
|-- build/
|-- kernel/
| `-- include/
`-- library/
`-- include/
The build/ directory contains some scripts needed for building and testing the
os as well as a floppy image preinstalled with the GRUB bootloader.
@ -38,83 +38,83 @@ Next, basic Makefiles were added to os5/ and kernel/. The standard procedure of
the main makefile is to run the makefile in kernel/, copy the kernel image into
the floppy and then run bochs-term.
BUILDDIR := $(PWD)
PATH := /usr/local/cross/bin:$(PATH)
DIRS := kernel
TARGET := i386-elf
:::make
BUILDDIR := $(PWD)
AS := nasm
CC := i386-elf-gcc
LD := i386-elf-ld
ASFLAGS := -f elf
CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
CCFLAGS += -fomit-frame-pointer -fno-asynchronous-unwind-tables
CCFLAGS += -fno-unwind-tables -I$(BUILDDIR)/library/include
LDFLAGS := -T $(BUILDDIR)/library/include/Link.ld
export BUILDDIR AS CC LD ASFLAGS CCFLAGS LDFLAGS
.SILENT:
.PHONY: $(DIRS) floppy emul
.default: all floppy emul
l: all floppy emul
all: $(DIRS)
$(DIRS): force
@echo " MAKE " $@
@cd $@; $(MAKE) $(MFLAGS)
clean:
@for DIR in $(DIRS); do echo " CLEAN " $$DIR; cd $(BUILDDIR)/$$DIR; make clean; done;
floppy: force
@echo " UPDATING IMAGE"
@build/update_image.sh
PATH := /usr/local/cross/bin:$(PATH)
DIRS := kernel
TARGET := i386-elf
emul: force
@echo " STARTING EMULATOR"
@build/emul.sh
AS := nasm
CC := i386-elf-gcc
LD := i386-elf-ld
force:
true
{: .prettyprint}
ASFLAGS := -f elf
CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
CCFLAGS += -fomit-frame-pointer -fno-asynchronous-unwind-tables
CCFLAGS += -fno-unwind-tables -I$(BUILDDIR)/library/include
LDFLAGS := -T $(BUILDDIR)/library/include/Link.ld
export BUILDDIR AS CC LD ASFLAGS CCFLAGS LDFLAGS
.SILENT:
.PHONY: $(DIRS) floppy emul
.default: all floppy emul
l: all floppy emul
all: $(DIRS)
$(DIRS): force
@echo " MAKE " $@
@cd $@; $(MAKE) $(MFLAGS)
clean:
@for DIR in $(DIRS); do echo " CLEAN " $$DIR; cd $(BUILDDIR)/$$DIR; make clean; done;
floppy: force
@echo " UPDATING IMAGE"
@build/update_image.sh
emul: force
@echo " STARTING EMULATOR"
@build/emul.sh
force:
true
The makefile in the kernel/ directory is pretty much straight forward.
TARGET := kernel
SUBDIR := kernel
SOURCES := kinit.o boot.o
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s))
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c))
CCFLAGS += -Iinclude
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
.SUFFICES: .o .s .c
all: $(TARGET)
$(TARGET): $(SOURCES)
@echo " ln " $(TARGET)
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
.c.o:
@echo " gcc " $<
@$(CC) $(CFLAGS) -c $< -o $@
.s.o:
@echo " nasm " $<
@$(AS) $(ASFLAGS) $< -o $@
clean:
-@rm $(SOURCES) 2>/dev/null
-@rm $(TARGET) 2>/dev/null
{: .prettyprint}
:::make
TARGET := kernel
SUBDIR := kernel
SOURCES := kinit.o boot.o
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s))
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c))
CCFLAGS += -Iinclude
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
.SUFFICES: .o .s .c
all: $(TARGET)
$(TARGET): $(SOURCES)
@echo " ln " $(TARGET)
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
.c.o:
@echo " gcc " $<
@$(CC) $(CFLAGS) -c $< -o $@
.s.o:
@echo " nasm " $<
@$(AS) $(ASFLAGS) $< -o $@
clean:
-@rm $(SOURCES) 2>/dev/null
-@rm $(TARGET) 2>/dev/null

View File

@ -19,34 +19,33 @@ a special Linker file for the kernel.
*kernel/include/Link.ld*
ENTRY(start)
SECTIONS {
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000)
{
code = .; _code = .; __code = .;
*(.text)
*(.eh_frame)
. = ALIGN(4096);
}
.data : AT(ADDR(.data) - 0xC0000000)
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
. = ALIGN(4096);
}
.bss : AT(ADDR(.bss) - 0xC0000000)
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
_end = .;
}
{: .prettyprint .linenums}
ENTRY(start)
SECTIONS {
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000)
{
code = .; _code = .; __code = .;
*(.text)
*(.eh_frame)
. = ALIGN(4096);
}
.data : AT(ADDR(.data) - 0xC0000000)
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
. = ALIGN(4096);
}
.bss : AT(ADDR(.bss) - 0xC0000000)
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
_end = .;
}
GRUB drops us off at the kernel entry point - *start* as defined in the
linkfile - without paging and with an unknown GDT. So setting that up will be
@ -54,30 +53,30 @@ the first order of business. This is done in the assembly bootstrap.
*kernel/boot.s*
; Kernel start point
[global start]
start:
cli
; Load page directory and enable paging
mov ecx, BootPageDirectory - KERNEL_OFFSET
mov cr3, ecx
mov ecx, cr0
or ecx, 0x80000000
mov cr0, ecx
lea ecx, [.higherHalf]
jmp ecx
.higherHalf:
; Load GDT
mov ecx, gdt_ptr
lgdt [ecx]
SetSegments 0x10, cx
jmp 0x8:.gdtLoaded
.gdtLoaded:
{: .prettyprint .lang-nasm .linenums:61}
:::nasm
; Kernel start point
[global start]
start:
cli
; Load page directory and enable paging
mov ecx, BootPageDirectory - KERNEL_OFFSET
mov cr3, ecx
mov ecx, cr0
or ecx, 0x80000000
mov cr0, ecx
lea ecx, [.higherHalf]
jmp ecx
.higherHalf:
; Load GDT
mov ecx, gdt_ptr
lgdt [ecx]
SetSegments 0x10, cx
jmp 0x8:.gdtLoaded
.gdtLoaded:
Here's another new thing for me. Macros. Can't believe I could do without them
before. *SetSegments* is a macro that in this case loads 0x10 into ecx and then
@ -86,94 +85,93 @@ which also contains some constants.
*kernel/include/asm_macros.inc*
; GRUB multiboot headers
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
MBOOT_MEMORY_INFO_FLAG equ 1<<1
MBOOT_HEADER_MAGIC equ 0x1BADB002
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
MBOOT_HEADER_CHECKSUM equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)
KERNEL_OFFSET equ 0xC0000000
BOOT_STACK_SIZE equ 0x1FFF
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
%macro SetSegments 2
mov e%2, %1
mov ds, %2
mov es, %2
mov fs, %2
mov gs, %2
mov ss, %2
%endmacro
{: .prettyprint .lang-nasm .linenums:2}
:::nasm
; GRUB multiboot headers
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
MBOOT_MEMORY_INFO_FLAG equ 1<<1
MBOOT_HEADER_MAGIC equ 0x1BADB002
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
MBOOT_HEADER_CHECKSUM equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)
KERNEL_OFFSET equ 0xC0000000
BOOT_STACK_SIZE equ 0x1FFF
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
%macro SetSegments 2
mov e%2, %1
mov ds, %2
mov es, %2
mov fs, %2
mov gs, %2
mov ss, %2
%endmacro
There are also references to some data structures, i.e. *BootPageDirectory* and
*gdt_ptr*. Those are hardcoded in the bootstrap file.
*kernel/boot.s*
%include "include/asm_macros.inc"
[bits 32]
section .bss
align 0x8
; Stack for booting
[global BootStack]
BootStackTop:
resb BOOT_STACK_SIZE
BootStack:
section .data
align 0x1000
; Page directory for booting up.
; First four megabytes are identity mapped as well as
; mapped to 0xC0000000
[global BootPageDirectory]
BootPageDirectory:
dd (BootPageTable - KERNEL_OFFSET) + 0x3
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
dd (BootPageTable - KERNEL_OFFSET) + 0x3
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
BootPageTable:
%assign i 0
%rep 1024
dd (i << 12) | 0x3
%assign i i+1
%endrep
; Hard-coded GDT.
; GDT pointer is wrapped into the first entry
[global gdt]
gdt_ptr:
gdt:
dw 0x002F
dd gdt
dw 0x0000
dd 0x0000FFFF, 0x00CF9A00
dd 0x0000FFFF, 0x00CF9200
dd 0x0000FFFF, 0x00CFFA00
dd 0x0000FFFF, 0x00CFF200
section .text
align 4
; GRUB Multiboot data
MultiBootHeader:
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd MBOOT_HEADER_CHECKSUM
{: .prettyprint .lang-nasm .linenums:2}
:::nasm
%include "include/asm_macros.inc"
[bits 32]
section .bss
align 0x8
; Stack for booting
[global BootStack]
BootStackTop:
resb BOOT_STACK_SIZE
BootStack:
section .data
align 0x1000
; Page directory for booting up.
; First four megabytes are identity mapped as well as
; mapped to 0xC0000000
[global BootPageDirectory]
BootPageDirectory:
dd (BootPageTable - KERNEL_OFFSET) + 0x3
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
dd (BootPageTable - KERNEL_OFFSET) + 0x3
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
BootPageTable:
%assign i 0
%rep 1024
dd (i << 12) | 0x3
%assign i i+1
%endrep
; Hard-coded GDT.
; GDT pointer is wrapped into the first entry
[global gdt]
gdt_ptr:
gdt:
dw 0x002F
dd gdt
dw 0x0000
dd 0x0000FFFF, 0x00CF9A00
dd 0x0000FFFF, 0x00CF9200
dd 0x0000FFFF, 0x00CFFA00
dd 0x0000FFFF, 0x00CFF200
section .text
align 4
; GRUB Multiboot data
MultiBootHeader:
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd MBOOT_HEADER_CHECKSUM
Well. This is first of all some area saved for a stack. Then there's the page
directory which has the same page table at virtual memory 0x00000000 and
@ -191,29 +189,29 @@ from the page directory.
*kernel/boot.s*
.gdtLoaded:
; Clear the identity mapping from the page directory
mov edx, BootPageDirectory
xor ecx, ecx
mov [edx], ecx
invlpg[0]
; Load a stack for booting
mov esp, BootStack
mov ebp, BootStack
; eax contains the magic number from GRUB 0x2BADB002
push eax
; ebx contains the address of the Multiboot information structure
add ebx, KERNEL_OFFSET
push ebx
; Call the c function for setting up
[extern kinit]
call kinit
jmp $
{: .prettyprint .lang-nasm .linenums:83}
:::nasm
.gdtLoaded:
; Clear the identity mapping from the page directory
mov edx, BootPageDirectory
xor ecx, ecx
mov [edx], ecx
invlpg[0]
; Load a stack for booting
mov esp, BootStack
mov ebp, BootStack
; eax contains the magic number from GRUB 0x2BADB002
push eax
; ebx contains the address of the Multiboot information structure
add ebx, KERNEL_OFFSET
push ebx
; Call the c function for setting up
[extern kinit]
call kinit
jmp $
The final thing we do before jumping into the c kernel stub is push the values
of eax and ebx which contains the multiboot magic number and information
@ -223,12 +221,11 @@ The only thing that's left now in order to get this to run is the c stub.
*kernel/kinit.c*
void kinit()
{
}
{: .prettyprint .linenums:2}
:::c
void kinit()
{
}
Compiling and running this through bochs, we are presented with a black and
white screen completely void of error messages. Perfect!

View File

@ -6,13 +6,14 @@ tags: [osdev]
Something that always annoyed me is how hard it is to synchronize constants
between assembly and c code. In assembler, you define a constant value as
EXACT_PI equ 3
{: .prettyprint .lang-nasm}
:::nasm
EXACT_PI equ 3
and in c
#define EXACT_PI 3
{: .prettyprint .lang-c}
:::c
#define EXACT_PI 3
As is usually the case with things that annoy me, there is of course a solution
to this, as I found out today. The solution is the c preprocessor.
@ -30,27 +31,27 @@ Well, here's a minimal (non-working) example:
_myAsmFile.asm_
#include <header.h>
mov eax, EXACT_PI
{: .prettyprint .lang-nasm}
:::nasm
#include <header.h>
mov eax, EXACT_PI
_include/header.h_
#pragma once
#define EXACT_PI 3
#ifndef __ASSEMBLER__
// This is not evaluated if header.h is included from an assembly file.
#endif
{: .prettyprint .lang-c}
:::c
#pragma once
#define EXACT_PI 3
#ifndef __ASSEMBLER__
// This is not evaluated if header.h is included from an assembly file.
#endif
This is compiled through:
cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
nasm myAsmFile.s
{: .prettyprint}
:::bash
$ cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
$ nasm myAsmFile.s
The _-x_-flag tells the preprocessor what type of file the following input
files are. _assembler-with-cpp_ means _cpp_ will ignore everything but the

View File

@ -32,16 +32,21 @@ that each page only has room for four pointers. The figure below shows eight
physical pages of a such computer. The two leftmost pages are used by the
physical memory manager for the free page stack. The stack contains pointers to
the next five pages, who are free. The rightmost page is handed out.
![PMM1](/media/img/pmm1b.png){: .noborder .center}
When the _pmm_ receives a request for a memory page it will pop the topmost
entry from the stack and returns, in this case, the second rightmost page to
the caller. ![PMM2](/media/img/pmm2b.png){: .noborder .center}
the caller.
![PMM2](/media/img/pmm2b.png){: .noborder .center}
The next time the pmm receives a request for a memory page it will notice that
an entire page of the stack is empty and just being wasted, so it will shrink
its stack by one page-size and return the address of the page that previously
made up the top of the stack. ![PMM3](/media/img/pmm3b.png){: .noborder .center}
made up the top of the stack
![PMM3](/media/img/pmm3b.png){: .noborder .center}
Likewise, if the stack is full of pointers when a used page is handed back,
that page is used to increase the stack space. Through the use of virtual

View File

@ -54,7 +54,9 @@ looking up the fourth group in the page directory. It finds the address of the
page directory and assumes this is the page table for the group. It caries on,
looking up the second entry in this 'page table' and gets the address of the
page it wants. This happens to be the address of the page table for the second
group. ![VMM2](/media/img/vmm2b.png){: .center .noborder}
group.
![VMM2](/media/img/vmm2b.png){: .center .noborder}
In other words, you can access any page table through a fixed address in
memory. But wait, it gets even better.
@ -64,11 +66,13 @@ virtual memory. The MMU will look up the last entry in the page directory and
get the address of the page directory. It will then look up the last entry in
the page directory and get the address of the page directory (that's not a typo
- I meant to write the same thing twice). This lets you access the page
directory too through a fixed memory address. ![VMM3](/media/img/vmm3b.png){: .center .noborder}
directory too through a fixed memory address.
![VMM3](/media/img/vmm3b.png){: .center .noborder}
###Some considerations
An important question to put at this point is whether a recursive page
directory is really a good idea.
directory is really a good idea.
In our imaginary computer with its really small address space, we notice that
the page table and directories now reserve a quarter of the entire available
@ -78,7 +82,7 @@ address space, though, which is more reasonable. Then again, if your computer
has 4 gigabytes of physical RAM, this would mean there is four megabytes of it
that can't be used. Then again again, if you have easy access to your page
tables - such as through a recursive page directory - you can just page in
those 4 megabytes as needed.
those 4 megabytes as needed.
There are other simple ways of accessing the page directory and tables. For
example, if you just keep track of the page directory and one page table it's
@ -91,16 +95,16 @@ tool, others don't.
Finally, if a recursive page directory is used on an x86, the following can be
used to access the page directories and tables:
uint32_t *page_dir = 0xFFFFF000;
uint32_t *page_tables = 0xFFC00000;
//addr = virtual address
//phys = physical address (page alligned)
//flags = access flags
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
page_tables[addr >> 12] = phys | flags;
{:.prettyprint}
:::c
uint32_t *page_dir = 0xFFFFF000;
uint32_t *page_tables = 0xFFC00000;
//addr = virtual address
//phys = physical address (page alligned)
//flags = access flags
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
page_tables[addr >> 12] = phys | flags;
###Git
A recursive page directory has been implemented in Git commit

View File

@ -26,19 +26,19 @@ Now, let's look at a few ways to do this.
The simplest possible memory manager just hands out a new address each time
_malloc_ is called and doesn't care when memory is freed.
uint32_t memory_pointer = HEAP_START;
:::c
uint32_t memory_pointer = HEAP_START;
void *malloc(uint32_t size);
{
memory_pointer = memory_pointer + size;
return memory_pointer - size;
}
void *malloc(uint32_t size);
{
memory_pointer = memory_pointer + size;
return memory_pointer - size;
}
void free(void *mem)
{
;
}
{: .prettyprint}
void free(void *mem)
{
;
}
###Heap
The next method - which I prefer - is a memory heap.
@ -46,33 +46,33 @@ The next method - which I prefer - is a memory heap.
The heap consists of a list of free memory areas. In the simplest possible
variety, it would look something like this:
struct area_header
{
uint32_t size;
struct free_area *next;
};
void *malloc(uint32_t size)
{
struct area_header *area = heap_list_head;
while(area)
{
if(area->size >= size)
{
remove_from_heap_list(area);
return get_memory_area(area);
}
area = area->next;
}
panic("Out of memory!");
}
void free(void *mem)
{
struct area_header area = get_area_header(mem);
insert_into_heap_list(area);
}
{: .prettyprint}
:::c
struct area_header
{
uint32_t size;
struct free_area *next;
};
void *malloc(uint32_t size)
{
struct area_header *area = heap_list_head;
while(area)
{
if(area->size >= size)
{
remove_from_heap_list(area);
return get_memory_area(area);
}
area = area->next;
}
panic("Out of memory!");
}
void free(void *mem)
{
struct area_header area = get_area_header(mem);
insert_into_heap_list(area);
}
Here it is assumed that the free memory is already divided into smaller areas
that each start with an *area_header* structure as in the figure below. If the

View File

@ -7,8 +7,8 @@ tags: [osdev]
Today I was writing some code for handling interrupts.
At one point I needed the following piece of code
extern void isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void), isr47(void);
{: .prettyprint}
:::c
extern void isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void), isr47(void);
###The solution
Vim macros.
@ -17,7 +17,7 @@ I've been using this site and my rewrite of my operating system as an excuse to
learn vim. And today it payed off. To write the above piece of code I used the
key presses
iisr0(void),<esc>0qayyp3l<ctrl>a0q46@a47k48J$r;Iextern void <esc>
iisr0(void),<esc>0qayyp3l<ctrl>a0q46@a47k48J$r;Iextern void <esc>
Couldn't be easier!
@ -25,7 +25,7 @@ OK, so maybe it could... Let's break it down.
Let's start with
iisr0(void),<esc>
iisr0(void),<esc>
_i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave
Insert mode with the escape key.
@ -33,7 +33,7 @@ Insert mode with the escape key.
Next is _0_ to bring the pointer to the beginning of the line. Then comes the
macro.
qayyp3l<ctrl>a0q
qayyp3l<ctrl>a0q
_qa_ starts recording a macro into register a.
@ -48,13 +48,13 @@ recording.
The next part:
46@a47k48J
46@a47k48J
runs the macro 46 times, steps up 47 times and joins the current line with the
next 48 times. We now have
isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void),
{: .prettyprint}
:::c
isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void),
and all we need to do now is replace the last comma with a semicolon using
_$r;_ and insert _extern void_ at the beginning of the line using _I_.
@ -63,41 +63,41 @@ _$r;_ and insert _extern void_ at the beginning of the line using _I_.
Starting with
INTNOERR 0
{: .prettyprint}
:::nasm
INTNOERR 0
I used
I used
qayypcwINTNOERR<esc>$<ctrl>a0q
qsyypcwINTERR<esc>$<ctrl>a0q
dd6@a@s@a5@s33@a
qayypcwINTNOERR<esc>$<ctrl>a0q
qsyypcwINTERR<esc>$<ctrl>a0q
dd6@a@s@a5@s33@a
and ended up with
INTNOERR 0
INTNOERR 1
INTNOERR 2
INTNOERR 3
INTNOERR 4
INTNOERR 5
INTNOERR 6
INTNOERR 7
INTERR 8
INTNOERR 9
INTERR 10
INTERR 11
INTERR 12
INTERR 13
INTERR 14
INTNOERR 15
...
INTNOERR 45
INTNOERR 46
{: .prettyprint}
:::nasm
INTNOERR 0
INTNOERR 1
INTNOERR 2
INTNOERR 3
INTNOERR 4
INTNOERR 5
INTNOERR 6
INTNOERR 7
INTERR 8
INTNOERR 9
INTERR 10
INTERR 11
INTERR 12
INTERR 13
INTERR 14
INTNOERR 15
...
INTNOERR 45
INTNOERR 46
I love vim!
###Application
###Application
So where did I use this? I've been writing some code for handling interrupts in
the os. You can find it in Git commit
[26dd8e4c75](https://github.com/thomasloven/os5/tree/26dd8e4c7507b66e4f94bf2c4e980265c6f0a20b).

View File

@ -34,7 +34,7 @@ To find out whether the device has a front facing camera or not it looks into
the property list of _Springboard_-the main interface of iOS. This is located
at
/System/Library/CoreServices/Springboard.app/
/System/Library/CoreServices/Springboard.app/
and is called either N??AP.plist or K??AP.plist on an iPhone or an iPad
respectively where ?? is a number that seems to vary with your model. In there

View File

@ -14,16 +14,16 @@ popular. With segmentation, the physical memory is divided into segments that
work as a kind of translation table. In Protected mode, if you call an address
like
jmp CS:AX
{: .prettyprint .lang-nasm}
:::nasm
jmp CS:AX
the processor looks into the currently loaded __Local__ or __Global Descriptor
Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or
__Segment Descriptor__) describes the beginning of a segment which is combined
with the offset in _AX_ to get the physical address;
physical_address = segment_descriptor_from_index(CS).base + AX;
{: .prettyprint}
:::c
physical_address = segment_descriptor_from_index(CS).base + AX;
The segment descriptor also has a limit, which in our example is the maximum
value _AX_ is allowed to take. If it's higher, you get a __Segmentation Fault__
@ -76,11 +76,11 @@ Changing the CPL is actually two different problems.
Increasing the CPL is relatively easy. It can be done either through a far jump
JMP 0x1B:label
label:
; The CS selector is now 0x18 | 0x3
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
{: .prettyprint .lang-nasm}
:::nasm
JMP 0x1B:label
label:
; The CS selector is now 0x18 | 0x3
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
or through the `IRET` instruction
@ -119,32 +119,33 @@ bottom two bits to 0x3, you will soon be in User Mode.
An other (better in my opinion) option is to create a fake interrupt-pushed
stack and push that onto the stack before running `IRET` .
// C code
struct
{
uint32_t esp;
uint32_t ss;
uint32_t eflags;
uint32_t eip;
uint32_t cs;
} fake_stack;
:::c
// C code
struct
{
uint32_t esp;
uint32_t ss;
uint32_t eflags;
uint32_t eip;
uint32_t cs;
} fake_stack;
fake_stack.esp = usermode_stack_top;
fake_stack.ss = user_data_segment | 0x3;
fake_stack.eflags = 0;
fake_stack.eip = &usermode_function;
fake_stack.cs = user_code_segment | 0x3;
set_all_segments(user_data_segment | 0x3);
run_iret(&fake_stack);
{: .prettyprint}
fake_stack.esp = usermode_stack_top;
fake_stack.ss = user_data_segment | 0x3;
fake_stack.eflags = 0;
fake_stack.eip = &usermode_function;
fake_stack.cs = user_code_segment | 0x3;
; Assembler code
run_iret:
add esp, 0x8
iret
{: .prettyprint .lang-nasm}
set_all_segments(user_data_segment | 0x3);
run_iret(&fake_stack);
&nbsp;
:::nasm
; Assembler code
run_iret:
add esp, 0x8
iret
###Going back to ring0

View File

@ -53,11 +53,11 @@ hard in fact that most tutorials actually do. Here's how it's really done
(source: [Intel
Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/))
gdt[TSS_DESCRIPTOR].base = &tss;
gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
gdt[TSS_DESCRIPTOR].flags = 0;
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED;
{: .prettyprint}
:::c
gdt[TSS_DESCRIPTOR].base = &tss;
gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
gdt[TSS_DESCRIPTOR].flags = 0;
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED;
What most tutorials get wrong is the limit field. The TSS is actually a memory
segment, and like all memory segments it has a segment descriptor in the GDT

View File

@ -39,95 +39,96 @@ Since the return address is stored on the stack, if you were to switch
stacks inside a function, when you return, you'll be somewhere else.
This is a common way of making usermode threads. Ponder the following:
void switch_thread()
{
push_all_registers();
switch_stack_pointer();
pop_all_registers();
return;
}
void a()
{
while(1)
{
do_something();
switch_thread();
}
}
:::c
void switch_thread()
{
push_all_registers();
switch_stack_pointer();
pop_all_registers();
return;
}
void b()
{
while(1)
{
do_something_else();
switch_thread();
}
}
void a()
{
while(1)
{
do_something();
switch_thread();
}
}
void b()
{
while(1)
{
do_something_else();
switch_thread();
}
}
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__
runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
running. The top of the stacks looks like:
+-----------------------+
|switch_stack_pointer RA|
|all registers |
+----------ESP----------+ |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+-----------------------+
|switch_stack_pointer RA|
|all registers |
+----------ESP----------+ |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
where `RA` means Return Address and `ESP` is where the stack pointer is
currently pointing.
As execution of __A__ continues, the processor will `do_something()` and
then call `switch_thread()`...
+-----------------------+
|switch_stack_pointer RA|
+----------ESP----------+ |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+-----------------------+
|switch_stack_pointer RA|
+----------ESP----------+ |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
`switch_thread()` pushes all registers to the stack and calls
`switch_stack_pointer()`
+----------ESP----------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+----------ESP----------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
`switch_stack_pointer()` performs some scheduling to find out which
thread is to run next, and then switches the stack pointer over to the
top of __B__'s stack.
+-----------------------+ +----------ESP----------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+-----------------------+ +----------ESP----------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
The processor keeps on executing code, and `switch_stack_pointer()` soon
returns
+-----------------------+
|switch_stack_pointer RA| +----------ESP----------+
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+-----------------------+
|switch_stack_pointer RA| +----------ESP----------+
|all registers | |all registers |
|switch_thread RA | |switch_thread RA |
|a RA | |b RA |
| ... | | ... |
`switch_thread()` pops all registers and returns...
+-----------------------+
|switch_stack_pointer RA|
|all registers |
|switch_thread RA | +----------ESP----------+
|a RA | |b RA |
| ... | | ... |
{: .nopretty}
+-----------------------+
|switch_stack_pointer RA|
|all registers |
|switch_thread RA | +----------ESP----------+
|a RA | |b RA |
| ... | | ... |
... and we're now in `b()` with all registers of __B__ loaded.
###Stacks in the kernel
@ -144,38 +145,41 @@ Molloys](http://www.jamesmolloy.co.uk/tutorial_html/) or [Brandon
Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
have something like this to handle interrupts:
int_stub:
pusha
xor eax, eax
mov ax, ds
push eax
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call int_handler
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret
{: .lang-nasm}
:::nasm
int_stub:
pusha
void int_handler(registers_t r)
{
do_stuff();
}
xor eax, eax
mov ax, ds
push eax
mov eax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call int_handler
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret
&nbsp;
:::c
void int_handler(registers_t r)
{
do_stuff();
}
In fact, if you've been following one of those tutorials, you probably
have the above code twice, for some reason...
@ -184,42 +188,45 @@ Anyway. This would take care of both pushing and poping all registers,
and with only a small modification, it becomes very easy to switch the
stacks too...
int_stub:
pusha
xor eax, eax
mov ax, ds
push eax
mov eax 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ;Pass stack pointer to int_handler
call int_handler
mov esp, eax ;int_handler returns a new stack pointer
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret
{: .lang-nasm }
:::nasm
int_stub:
pusha
registers_t *int_handler(registers_t *r)
{
do_stuff();
r = get_next_thread(r);
return r;
}
xor eax, eax
mov ax, ds
push eax
mov eax 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ;Pass stack pointer to int_handler
call int_handler
mov esp, eax ;int_handler returns a new stack pointer
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret
&nbsp;
:::c
registers_t *int_handler(registers_t *r)
{
do_stuff();
r = get_next_thread(r);
return r;
}
This gives a pointer to the threads registers as input to the ISR and
expect a pointer to some registers in return. They may or may not be the
@ -246,56 +253,55 @@ single data structure. So let's think about it for a while.
While the thread is running we want some information stored somewhere in
kernel space about it.
+-----------------------+
|thread information |
+-----------------------+
{: .nopretty}
+-----------------------+
|thread information |
+-----------------------+
Then, when an interrupt or syscall happens, a new stack is loaded
and some stuff is pushed onto it. If we want this near our thread
information it will have to go right before it, since the stack grows
backwards.
+-----------------------+
|thread registers |
|thread information |
+-----------------------+
{: .nopretty}
+-----------------------+
|thread registers |
|thread information |
+-----------------------+
Finally, we want the kernel mode stack. Well... the stack pointer is
right at the start of the registers now, so why not just continue the
stack from there?
+-----------------------+
| ... |
|kernel mode stack |
|thread registers |
|thread information |
+-----------------------+
{: .nopretty}
+-----------------------+
| ... |
|kernel mode stack |
|thread registers |
|thread information |
+-----------------------+
###Setting this up
To set this up, the thread information structure has to be set up
something like:
struct thread_info_struct
{
uint8_t stack_space[KERNEL_STACK_SIZE];
registers_t r;
struct thread_data_struct thread_data;
} my_thread_info;
:::c
struct thread_info_struct
{
uint8_t stack_space[KERNEL_STACK_SIZE];
registers_t r;
struct thread_data_struct thread_data;
} my_thread_info;
When the thread is running in user mode, the TSS should be set up in
such a way that the stack pointer loaded at an interrupt points to the
end of the registers, i.e. the beginning of the thread data.
TSS.esp0 = &my_thread_info.thread_data;
:::c
TSS.esp0 = &my_thread_info.thread_data;
And that's really all there is to it. Unbelievable, really, how many
years it took for me to figure this out.
In the process, I've found inspiration in [Rhombus by Nick
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
[linux](http://www.linux.org).
###Some considerations
@ -335,15 +341,16 @@ I recently learned - the hard way - that the [clang
compiler](http://clang.llvm.org) does not use this calling convention
for functions which do not in turn call other functions. I.e
int double_integer(int a)
{
return 2*a;
}
:::c
int double_integer(int a)
{
return 2*a;
}
int main(int argc, char **argv)
{
double_integer(5);
}
int main(int argc, char **argv)
{
double_integer(5);
}
If this code is compiled with clang `double_integer` will (in some
cases) not push `ebp` to stack.

View File

@ -21,11 +21,12 @@ what I did, and then I branched off the last commit I blogged about
For future reference (I'll probably cheat again someday) there were the commands for this:
git commit -m 'Bad excuse for not checking in before'
git checkout -b new_master OLD_COMMIT_SHA
git merge --strategy=ours master
git checkout master
git merge new_master
:::bash
$ git commit -m 'Bad excuse for not checking in before'
$ git checkout -b new_master OLD_COMMIT_SHA
$ git merge --strategy=ours master
$ git checkout master
$ git merge new_master
The resulting commit is found at
[f74ec287db](https://github.com/thomasloven/os5/tree/f74ec287db488a7bda5

View File

@ -41,8 +41,8 @@ boasts expressive error messages as a feature...
Anyway with clang version 3.1 you can compile i386-elf object files through
> clang -ccc-host-triple i386-pc-linux -c source.c -o object.o
{: .prettyprint .lang-sh}
:::bash
$ clang -ccc-host-triple i386-pc-linux -c source.c -o object.o
`-ccc-host-triple` isn't mentioned even once in the documentation of
clang nor are the possible choices. `i386-elf` which is somewhat of a
@ -66,8 +66,8 @@ If you run OSX clang 3.1 is installed with the
current version of Xcode. Version 3.2 is installed by
[Homebrew](http://mxcl.github.com/homebrew/).
> homebrew install llvm
{: .prettyprint .lang-sh}
:::bash
$ homebrew install llvm
Binutils
--------
@ -82,12 +82,14 @@ Instead, again, I use Homebrew.
First of all, to get a working cross target binutils, the brew formula
will have to be changed a bit
> brew edit binutils
:::bash
$ brew edit binutils
Change the last configure flag (` --enable-targets=x86_64-elf,arm-none-eabi,m32r`)
to `--target=i386-elf`. I also changed the `--program-prefix` to `i386-elf-`. Save the file and run
> brew install binutils
:::bash
$ brew install binutils
and you're good to go.
@ -122,48 +124,53 @@ a text mode (curses mode).
So I went out on a whim and tried
> brew info qemu
:::bash
$ brew info qemu
By now you should know pretty much what I think of Homebrew, so the
results of that command pretty much sealed the deal.
Now I run my kernel in qemu through
> qemu-system-i386 -kernel kernel/kernel -curses
:::bash
$ qemu-system-i386 -kernel kernel/kernel -curses
Qemu also turned out to have a monitor mode which contains some of the
functions I used most often in the bochs debugger, such as printing the
memory map. Further, this could be accessed using telnet from a
different tmux pane.
#!/bin/bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
tmux select-pane -L
telnet localhost 4444
{: .prettyprint}
:::bash
#!/bin/bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
tmux select-pane -L
telnet localhost 4444
Finally, I also installed an i386-elf targeted version of gdb - using Homebrew, obviously, with the same trick as for binutils. Gdb is found in a different tap of homebrew, so that will have to be installed first
> brew tap homebrew/dupes
> brew edit gdb
:::bash
$ brew tap homebrew/dupes
$ brew edit gdb
Add the flag `--target=i386-elf` to the configure flags, save and
> brew install gdb
:::bash
$ brew install gdb
This will link to `ì386-elf-gdb` and can be run in yet another tmux window.
#!/bin/bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
tmux select-pane -L
tmux slit-window -v 'i386-elf-gdb'
tmux select-pane -U
telnet localhost 4444
:::bash
#!/bin/bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
tmux select-pane -L
tmux slit-window -v 'i386-elf-gdb'
tmux select-pane -U
telnet localhost 4444
Upon start, gdb will look for a file called `.gdbinit` which in my case contains
file kernel/kernel
target remote localhost:1234
file kernel/kernel
target remote localhost:1234
Results
-------

View File

@ -1,6 +1,7 @@
layout: post
title: "Two Small Projects"
subtitle: "Audio and Arduino"
tags: [electronics]
###Breadboard Arduino
I didn't really like the arduino when I first heard about it. It

View File

@ -16,43 +16,43 @@ should not affect the other.
For example:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
int variable = 1;
int pid = fork();
int status;
if( pid )
{
// This is the parent
printf("Parent says: %d\n", variable);
variable = 2;
printf("Parent says: %d\n", variable);
waitpid(pid, &status, 0); // Let the child run
printf("Parent says: %d\n", variable);
} else {
// This is the child
printf("Child says: %d\n", variable);
variable = 3;
printf("Child says: %d\n", variable);
}
return 0;
}
{: .lang-c}
:::c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
int variable = 1;
int pid = fork();
int status;
if( pid )
{
// This is the parent
printf("Parent says: %d\n", variable);
variable = 2;
printf("Parent says: %d\n", variable);
waitpid(pid, &status, 0); // Let the child run
printf("Parent says: %d\n", variable);
} else {
// This is the child
printf("Child says: %d\n", variable);
variable = 3;
printf("Child says: %d\n", variable);
}
return 0;
}
This simple program should output (assuming the parent is run first and
is not interrupted):
Parent says: 1
Parent says: 2
Child says: 1
Child says: 3
Parent says: 2
Parent says: 1
Parent says: 2
Child says: 1
Child says: 3
Parent says: 2
The virtual memory of the X86 architecture allows us to switch out the
entire memory space in one strike, and that allows for this behavior.
@ -93,6 +93,7 @@ Finally, each area has a pointer to its owning process.
Let's follow a memory area during part of a process' life.
###Setup
![PROCMM1](/media/img/procmm1.png){: .center .noborder}
In the figure above we see two processes, _A_ and _B_.
@ -112,13 +113,15 @@ In other words, it is two memory pages long (assuming 4kb pages).
The user types
> gcc hello_world.c
:::bash
$ gcc hello_world.c
into the terminal and the shell program executes the `fork` system call.
This makes the kernel do a lot of things, one of which is create a new
memory map for the new process. It then clones all memory areas into the
new map.
![PROCMM2](/media/img/procmm2.png){: .center .noborder}
The write flag of our area is unset and the CoW flag is set. The area is
@ -149,6 +152,7 @@ A while later, the parent process is scheduled in and it may also wish
to write to the stack. This time the area is already split in two, and
the required area has no copies, so it is just set as read/write and
we're done.
![PROCMM4](/media/img/procmm4.png){: .center .noborder}
Actually, the parent process will probably perform a `waitpid` syscall

View File

@ -6,15 +6,15 @@ tags: [osdev]
System calls is the way user processes communicate to the kernel. Look
at the following program, for example.
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, world!");
return 0;
}
{: .lang-c}
:::c
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, world!");
return 0;
}
When you call the program, even before it is started, the shell makes a
couple of system calls such as `fork()` and `exec()`. The program itself
@ -37,20 +37,21 @@ implemented it in my kernel yet, but here's how it would work.
####User side
First the definition in the c library:
int read(int file, char *ptr, int len)
{
return _syscall_read(file, ptr, len);
}
:::c
int read(int file, char *ptr, int len)
{
return _syscall_read(file, ptr, len);
}
Simply a wrapper for an assembly function:
[global _syscall_read]
_syscall_read:
mov eax, SYSCALL_READ
int 0x80
mov [syscall_error], edx
ret
{: .lang-nasm}
:::nasm
[global _syscall_read]
_syscall_read:
mov eax, SYSCALL_READ
int 0x80
mov [syscall_error], edx
ret
This function puts an identifier for the system call in the `eax`
register and then execute the system call interrupt.
@ -62,37 +63,39 @@ Conventions](http://wiki.osdev.org/Calling_Conventions) more carefully.
Of course, this can be simplified with a macro to
[global _syscall_read]
DEF_SYSCALL(read, SYSCALL_READ)
{: .lang-nasm}
:::nasm
[global _syscall_read]
DEF_SYSCALL(read, SYSCALL_READ)
####Kernel side
In the kernel, the system call is caught by the following function:
registers_t *syscall_handler(registers_t *r)
{
if(syscall_handlers[r->eax])
r = syscall_handlers[r->eax](r);
else
r->edx = ERR_NOSYSCALL;
return r;
}
:::c
registers_t *syscall_handler(registers_t *r)
{
if(syscall_handlers[r->eax])
r = syscall_handlers[r->eax](r);
else
r->edx = ERR_NOSYSCALL;
return r;
}
If the system call is registered correctly in the kernel (through the
macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything
onto the following function:
KDEF_SYSCALL(read, r)
{
process_stack stack = init_pstack();
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
:::c
KDEF_SYSCALL(read, r)
{
process_stack stack = init_pstack();
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
r->edx = errno;
return r;
}
return r;
}
The `init_pstack()` macro expands to `(unitptr_t *)(r->useresp + 0x4)`
and this lets us read the arguments passed to the system call from where
@ -100,10 +103,11 @@ they are pushed on call.
Then the `read()` function has the same definition as the library version.
int read(int file, char *ptr, int len)
{
...
}
:::c
int read(int file, char *ptr, int len)
{
...
}
_Spoiler alert:_ Keeping a version of `read()` (and in fact every
syscall function) inside the kernel will turn out to have some really

View File

@ -40,19 +40,19 @@ compiling newlib with that setup is rather annoying.
I'm also a fan of clean makefiles. Take a look at this:
VPATH := ../src
CC := i586-pc-myos-gcc
TARGETS := $(shell find ../src -name "*.c")
TARGETS := $(notdir $(TARGETS))
TARGETS := $(patsubst %.c, %, $(TARGETS))
all: $(TARGETS)
clean:
-rm $(TARGETS) 2>/dev/null
{: .lang-make}
:::make
VPATH := ../src
CC := i586-pc-myos-gcc
TARGETS := $(shell find ../src -name "*.c")
TARGETS := $(notdir $(TARGETS))
TARGETS := $(patsubst %.c, %, $(TARGETS))
all: $(TARGETS)
clean:
-rm $(TARGETS) 2>/dev/null
That's the makefile for the entire `/bin` directory in my os.
@ -67,13 +67,14 @@ make a formula for it. So after applying the patches described in the
post (I even kept the name `i586-pc-myos` since I don't have a working
name for my kernel besides an iteration number...) I did
export TARGET=i586-pc-myos
export PREFIX=/usr/local/Cellar/osdev/1.0
# Configure, build and install binutils
brew link osdev
# Configure, build and install gcc and libgcc
brew unlink osdev
brew link osdev
:::bash
$ export TARGET=i586-pc-myos
$ export PREFIX=/usr/local/Cellar/osdev/1.0
# Configure, build and install binutils
$ brew link osdev
# Configure, build and install gcc and libgcc
$ brew unlink osdev
$ brew link osdev
And that prepared me for building newlib.
@ -85,50 +86,52 @@ automake and autoconf since a couple of versions. More specifically,
you need automake version 1.12 or earlier and autoconf version 2.64.
Unfortunately, those versions are not available through Homebrew, so ...
curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
tar -zxf automake-1.12.tar.gz
mkdir -p build-automake
pushd build-automake
../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
make all -j
make install
popd
curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
tar -zxf autoconf-2.64.tar.gz
pushd build-autoconf
../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
make all -j
make install
popd
brew switch automake 1.12
brew switch autoconf 2.64
:::bash
$ curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
$ tar -zxf automake-1.12.tar.gz
$ mkdir -p build-automake
$ pushd build-automake
$ ../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
$ make all -j
$ make install
$ popd
$ curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
$ tar -zxf autoconf-2.64.tar.gz
$ pushd build-autoconf
$ ../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
$ make all -j
$ make install
$ popd
$ brew switch automake 1.12
$ brew switch autoconf 2.64
Those last two lines tells Homebrew that you want to use those specific
versions for now.
Now for the neat part. I followed the wiki post and used the
Now for the neat part. I followed the wiki post and used the
[syscall interface](/blog/2013/06/System-Calls) i've described earlier
but I also wrapped `crt0.S` and `syscalls.c` in
#ifndef KERNEL_MODE
...
#endif
:::c
#ifndef KERNEL_MODE
...
#endif
Then I built it all through
pushd build-newlib
../newlib/configure --target=$TARGET --prefix=$PREFIX
export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
make -j
make install
mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
rm -rf *
../newlib/configure --target=$TARGET --prefix=$PREFIX
export CPPFLAGS_FOR_TARGET=
make -j
make install
popd
:::bash
$ pushd build-newlib
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
$ export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
$ make -j
$ make install
$ mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
$ rm -rf *
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
$ export CPPFLAGS_FOR_TARGET=
$ make -j
$ make install
$ popd
This gives me two versions of the newlib c library. One with all syscalls
defined and one without. The latter is suitable for linking into the
@ -160,24 +163,26 @@ compile the kernel using a very simple makefile.
Somewhat simplified:
OBJECTS := $(shell find . -name "*.S")
OBJECTS += $(shell find . -name "*.c")
OBJECTS := $(OBJECTS:%.S=%.o)
OBJECTS := $(OBJECTS:%.c=%.o)
:::make
OBJECTS := $(shell find . -name "*.S")
OBJECTS += $(shell find . -name "*.c")
OBJECTS := $(OBJECTS:%.S=%.o)
OBJECTS := $(OBJECTS:%.c=%.o)
CC := i586-pc-myos-gcc
LDFLAGS := -nostdlib -T linkfile.ld
LDLIBS := -lkernel
CC := i586-pc-myos-gcc
LDFLAGS := -nostdlib -T linkfile.ld
LDLIBS := -lkernel
kernel: $(OBJECTS)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
kernel: $(OBJECTS)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
and everything else is taken care of by the default rules of gnu make,
including preprocessing and assembling .S files.
For executables running under my operating system it's even easier
CC := i586-pc-myos-gcc
:::make
CC := i586-pc-myos-gcc
That's all.

View File

@ -10,23 +10,24 @@ where to find everything. It has the following structure. The
[ELF specification](http://www.skyfree.org/linux/references/ELF_Format.pdf)
gives an excellent description on the meaning and use of each field.
typedef struct
{
uint8_t identity[16];
uint16_t type;
uint16_t machine;
uint32_t version;
uint32_t entry;
uint32_t ph_offset;
uint32_t sh_offset;
uint32_t flags;
uint16_t header_size;
uint16_t ph_size;
uint16_t ph_num;
uint16_t sh_size;
uint16_t sh_num;
uint16_t strtab_index;
}__attributes__((packed)) elf_header;
:::c
typedef struct
{
uint8_t identity[16];
uint16_t type;
uint16_t machine;
uint32_t version;
uint32_t entry;
uint32_t ph_offset;
uint32_t sh_offset;
uint32_t flags;
uint16_t header_size;
uint16_t ph_size;
uint16_t ph_num;
uint16_t sh_size;
uint16_t sh_num;
uint16_t strtab_index;
}__attributes__((packed)) elf_header;
The first thing we should do is check whether we actually got an
executable ELF file. (In the following code, I'll assume the entire elf
@ -38,31 +39,33 @@ identity field. The first four bytes of this filed should always be
`0x7F`,`'E'`,`'L'`,`'F'`. If that's correct, we can look at the `type`
field. For an executable standalone program, this should be `2`.
int load_elf(uint8_t *data)
{
elf_header *elf = (elf_header *)data;
if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
return -1;
...
:::c
int load_elf(uint8_t *data)
{
elf_header *elf = (elf_header *)data;
if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
return -1;
...
`is_elf` looks as follows. Note the use of `strncmp` which I can do
because I link [newlib into my kernel](/blog/2013/08/Catching-Up/).
int is_elf(elf_header *elf)
{
int iself = -1;
if((elf->identity[0] == 0x7f) && \
!strncmp((char *)&elf->identity[1], "ELF", 3))
{
iself = 0;
}
if(iself != -1)
iself = elf->type;
return iself;
}
:::c
int is_elf(elf_header *elf)
{
int iself = -1;
if((elf->identity[0] == 0x7f) && \
!strncmp((char *)&elf->identity[1], "ELF", 3))
{
iself = 0;
}
if(iself != -1)
iself = elf->type;
return iself;
}
Should be pretty straight forward. Let's continue.
@ -70,35 +73,37 @@ For just loading a simple ELF program, we only need to look at the
program headers which are located in a table at offset `ph_offset` in
the file.
typedef struct
{
uint32_t type;
uint32_t offset;
uint32_t virtual_address;
uint32_t physical_address;
uint32_t file_size;
uint32_t mem_size;
uint32_t flags;
uint32_t align;
}__attributes__((packed)) elf_phead;
:::c
typedef struct
{
uint32_t type;
uint32_t offset;
uint32_t virtual_address;
uint32_t physical_address;
uint32_t file_size;
uint32_t mem_size;
uint32_t flags;
uint32_t align;
}__attributes__((packed)) elf_phead;
The program headers each tell us about one section of the file, and we
use them to find out what parts of the elf image should be loaded where
in memory. So, the next step would be to go through all program headers
looking for loadable sections and load them into memory.
...
elf_phead *phead = (elf_phead)&data[elf->ph_offset];
uint32_t i;
for(i = 0; i < elf->ph_num; i++)
{
if(phead[i].type == ELF_PT_LOAD)
{
load_elf_segment(data, &phead[i]);
}
}
return 0;
}
:::c
...
elf_phead *phead = (elf_phead)&data[elf->ph_offset];
uint32_t i;
for(i = 0; i < elf->ph_num; i++)
{
if(phead[i].type == ELF_PT_LOAD)
{
load_elf_segment(data, &phead[i]);
}
}
return 0;
}
This would also be a good time to update the memory manager information
about the executable. You might want to keep track of the start and end
@ -106,25 +111,26 @@ of code and data for example.
Anyway, `load_elf_segment()` looks like this
void load_elf_segment(uint8_t *data, elf_phead *phead)
{
uint32_t memsize = phead->mem_size; // Size in memory
uint32_t filesize = phead->file_size; // Size in file
uint32_t mempos = phead->virtual_address; // Offset in memory
uint32_t filepos = phead->offset; // Offset in file
:::c
void load_elf_segment(uint8_t *data, elf_phead *phead)
{
uint32_t flags = MM_FLAG_READ;
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE;
uint32_t memsize = phead->mem_size; // Size in memory
uint32_t filesize = phead->file_size; // Size in file
uint32_t mempos = phead->virtual_address; // Offset in memory
uint32_t filepos = phead->offset; // Offset in file
new_area(current->proc, mempos, mempos + memsize, \
flags, MM_TYPE_DATA);
uint32_t flags = MM_FLAG_READ;
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE;
if(memsize == 0) return;
new_area(current->proc, mempos, mempos + memsize, \
flags, MM_TYPE_DATA);
memcpy(mempos, &data[filepos], filesize);
memset(mempos + filesize, 0, memsize - filesize);
}
if(memsize == 0) return;
memcpy(mempos, &data[filepos], filesize);
memset(mempos + filesize, 0, memsize - filesize);
}
Let's go through it.

View File

@ -38,7 +38,8 @@ What? But my code didn't even run!
I reapplied my changes one at a time and found the line that caused the
problem.
int i = 0;
:::c
int i = 0;
What?? That's it? Declaring a variable?

View File

@ -1,7 +1,7 @@
layout: post
title: "Virtual File System 2"
subtitle: "for real this time."
tags: [osdev]
tags: [osdev, filesystems]
Once again, several months have passed since I wrote anything here.
I also worked very little on the kernel during this time, though. So no
@ -33,25 +33,26 @@ generated on-the-fly by the kernel, network connections(?) etc.
Further, I want the VFS to be independent of any disk file system, e.g.
the VFS shouldn't have user-group-other read-write-execute tuples just
because it's designed with ext2 in mind. It might have those tuples -
because it's designed with ext2 in mind. It might have those tuples -
I haven't decided yet - but it it does it won't be because ext2 uses
them. Nor will the VFS be designed with ext2 or any other disk file
system in mind.
The VFS should offer the functions
open() // Open or create a file
close() // Close a file
read() // Read data from opened file
write() // Write data to opened file
move() // Move a file or directory
link() // Put a file or directory in path tree
unlink() // Remove a file or directory from path tree
stat() // Get more info about a file or directory
isatty() // Returns true if the file is a terminal
mkdir() // Create a directory
readdir() // Get a directory entry
finddir() // Find a file by name from a directory
:::c
open() // Open or create a file
close() // Close a file
read() // Read data from opened file
write() // Write data to opened file
move() // Move a file or directory
link() // Put a file or directory in path tree
unlink() // Remove a file or directory from path tree
stat() // Get more info about a file or directory
isatty() // Returns true if the file is a terminal
mkdir() // Create a directory
readdir() // Get a directory entry
finddir() // Find a file by name from a directory
for all files and directories regardless of their underlying device or
driver.
@ -96,20 +97,18 @@ Example:
A good starting point for the inode structure might be some pointers to
allow it to be placed in a tree, then.
:::c
struct vfs_node_st;
typedef vfs_node_t * INODE;
struct vfs_node_st;
typedef vfs_node_t * INODE;
typedef struct vfs_node_st
{
char name[VFS_NAME_SZ];
INODE parent;
INODE child;
INODE older, younger;
uint32_t type;
} vfs_node_t;
typedef struct vfs_node_st
{
char name[VFS_NAME_SZ];
INODE parent;
INODE child;
INODE older, younger;
uint32_t type;
} vfs_node_t;
This does waste a bit of memory, since most inodes that are used by the
system won't be in the VFS tree, but four `size_t` isn't that much,
@ -125,113 +124,118 @@ to maintain.
Then, we need a way to keep track of the driver, i.e. the functions
called to access the file. To do this, I define a new struct:
typedef struct vfs_driver_st
{
uint32_t (*open)(INODE, uint32_t);
uint32_t (*close)(INODE);
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
uint32_t (*link)(INODE, INODE, const char *);
uint32_t (*unlink)(INODE, const char *);
uint32_t (*stat)(INODE, struct stat *st);
uint32_t (*isatty)(INODE);
uint32_t (*mkdir)(INODE, const char *);
dirent_t *(*readdir)(INODE, uint32_t);
INODE (*finddir)(INODE, const char *);
} vfs_driver_t;
:::c
typedef struct vfs_driver_st
{
uint32_t (*open)(INODE, uint32_t);
uint32_t (*close)(INODE);
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
uint32_t (*link)(INODE, INODE, const char *);
uint32_t (*unlink)(INODE, const char *);
uint32_t (*stat)(INODE, struct stat *st);
uint32_t (*isatty)(INODE);
uint32_t (*mkdir)(INODE, const char *);
dirent_t *(*readdir)(INODE, uint32_t);
INODE (*finddir)(INODE, const char *);
} vfs_driver_t;
and add `vfs_driver_t *d` to the inode struct. I also added a length
value, a void pointer for arbitrary data used by the drivers and a flags
value - also for use by the drivers. The
inode struct now looks like this:
typedef struct vfs_node_st
{
char name[VFS_NAME_SZ];
void *parent;
void *child;
void *older, *younger;
uint32_t type;
vfs_driver_t *d;
void *data;
uint32_t flags;
uint32_t length;
}
:::c
typedef struct vfs_node_st
{
char name[VFS_NAME_SZ];
void *parent;
void *child;
void *older, *younger;
uint32_t type;
vfs_driver_t *d;
void *data;
uint32_t flags;
uint32_t length;
}
###Vfs functions
Next, I create some wrapper functions to call the driver functions.
uint32_t vfs_open(INODE ino, uint32_t mode)
{
if(ino->d->open)
return ino->d->open(ino, mode);
return 0;
}
:::c
uint32_t vfs_open(INODE ino, uint32_t mode)
{
if(ino->d->open)
return ino->d->open(ino, mode);
return 0;
}
and similar for all functions except `readdir` and `finddir` which
contain code to handle `.` and `..` for mount roots.
dirent_t *vfs_readdir(INODE ino, uint32_t num)
{
if(ino->type & FS_MOUNT)
{
if(num == 0)
{
dirent_t *ret = calloc(1, sizeof(dirent_t));
ret->ino = ino;
strcpy(ret->name, ".");
return ret;
} else if(num == 1) {
dirent_t *ret = calloc(1, sizeof(dirent_t));
ret->ino = ino->parent;
strcpy(ret->name, "..");
return ret;
}
}
if(ino->d->readdir)
return ino->d->readdir(ino, num);
return 0;
}
:::c
dirent_t *vfs_readdir(INODE ino, uint32_t num)
{
if(ino->type & FS_MOUNT)
{
if(num == 0)
{
dirent_t *ret = calloc(1, sizeof(dirent_t));
ret->ino = ino;
strcpy(ret->name, ".");
return ret;
} else if(num == 1) {
dirent_t *ret = calloc(1, sizeof(dirent_t));
ret->ino = ino->parent;
strcpy(ret->name, "..");
return ret;
}
}
if(ino->d->readdir)
return ino->d->readdir(ino, num);
return 0;
}
&nbsp;
INODE vfs_finddir(INODE ino, const char *name)
{
if(ino->type & FS_MOUNT)
{
if(!strcmp(name, "."))
{
return ino;
} else if(!strcmp(name, "..")) {
return ino->parent;
}
}
if(ino->d->finddir)
return ino->d->finddir(ino, name);
if(ino->d->readdir)
{
// Backup solution
int num = 0;
dirent_t *de;
while(1)
{
de = vfs_readdir(ino, num);
if(!de)
return 0;
if(!strcmp(name, de->name))
break;
free(de->name);
free(de);
num++;
}
INODE ret = de->ino;
free(de->name);
free(de);
return ret;
}
return 0;
}
:::c
INODE vfs_finddir(INODE ino, const char *name)
{
if(ino->type & FS_MOUNT)
{
if(!strcmp(name, "."))
{
return ino;
} else if(!strcmp(name, "..")) {
return ino->parent;
}
}
if(ino->d->finddir)
return ino->d->finddir(ino, name);
if(ino->d->readdir)
{
// Backup solution
int num = 0;
dirent_t *de;
while(1)
{
de = vfs_readdir(ino, num);
if(!de)
return 0;
if(!strcmp(name, de->name))
break;
free(de->name);
free(de);
num++;
}
INODE ret = de->ino;
free(de->name);
free(de);
return ret;
}
return 0;
}
Finally, I needed a function for mounting filesystems in the mount tree
and the `namei` function, which can actually be combined since they both
@ -241,38 +245,39 @@ _Warning:_ Pointer-pointers ahead!
First: a function for traversing the mount tree as far as possible
INODE vfs_find_root(char **path)
{
// Find closest point in mount tree
INODE current = vfs_root;
INODE mount = current;
char *name;
while((name = strsep(path, "/")))
{
current = current->child;
while(current)
{
if(!strcmp(current->name, name))
{
mount = current;
break;
}
current = current->olderyounger;
}
if(!current)
{
if(*path)
{
*path = *path - 1;
*path[0] = '/';
}
*path = name;
break;
}
}
return (INODE)mount;
}
:::c
INODE vfs_find_root(char **path)
{
// Find closest point in mount tree
INODE current = vfs_root;
INODE mount = current;
char *name;
while((name = strsep(path, "/")))
{
current = current->child;
while(current)
{
if(!strcmp(current->name, name))
{
mount = current;
break;
}
current = current->olderyounger;
}
if(!current)
{
if(*path)
{
*path = *path - 1;
*path[0] = '/';
}
*path = name;
break;
}
}
return (INODE)mount;
}
Pretty self explanatory. No? Well, `strsep` is a library function which
picks out one part of the path at a time and also advances the `path`
@ -284,69 +289,69 @@ The namei/mount function then uses this as a starting point:
INODE vfs_namei_mount(const char *path, INODE root)
{
char *npath = strdup(path);
char *pth = &npath[1];
// Find closest point in mount tree
INODE current = vfs_find_root(&pth);
char *name;
while(current && (name = strsep(&pth, "/")))
{
// Go through the path
INODE next = vfs_finddir(current, name);
if(root)
{
// If we want to mount someting
if(!next)
{
// Create last part of path if it doesn't exist
// But only if it is the last part.
if(pth)
return 0;
next = calloc(1, sizeof(vfs_node_t));
strcpy(next->name, name);
next->type = FS_DIRECTORY;
}
// Add path to mount tree
next->parent = current;
next->older = current->child;
current->child = next;
}
if(!next)
return 0;
if(!current->parent)
free(current);
current = next;
}
free(npath);
if(root && current->type == FS_DIRECTORY)
{
// Replace node in mount tree
root->parent = current->parent;
if(root->parent->child == current)
root->parent->child = root;
root->older = current->older;
if(root->older)
root->older->younger = current;
root->younger = current->younger;
if(root->younger)
root->younger->older = current;
strcpy(root->name, current->name);
root->type = FS_MOUNT;
if(current == vfs_root)
vfs_root = root;
free(current);
}
return current;
}
{: .lang-c}
:::c
INODE vfs_namei_mount(const char *path, INODE root)
{
char *npath = strdup(path);
char *pth = &npath[1];
// Find closest point in mount tree
INODE current = vfs_find_root(&pth);
char *name;
while(current && (name = strsep(&pth, "/")))
{
// Go through the path
INODE next = vfs_finddir(current, name);
if(root)
{
// If we want to mount someting
if(!next)
{
// Create last part of path if it doesn't exist
// But only if it is the last part.
if(pth)
return 0;
next = calloc(1, sizeof(vfs_node_t));
strcpy(next->name, name);
next->type = FS_DIRECTORY;
}
// Add path to mount tree
next->parent = current;
next->older = current->child;
current->child = next;
}
if(!next)
return 0;
if(!current->parent)
free(current);
current = next;
}
free(npath);
if(root && current->type == FS_DIRECTORY)
{
// Replace node in mount tree
root->parent = current->parent;
if(root->parent->child == current)
root->parent->child = root;
root->older = current->older;
if(root->older)
root->older->younger = current;
root->younger = current->younger;
if(root->younger)
root->younger->older = current;
strcpy(root->name, current->name);
root->type = FS_MOUNT;
if(current == vfs_root)
vfs_root = root;
free(current);
}
return current;
}
Note how `pth` is changed by `vfs_find_root()` to only contain the part
of the path that wasn't found. After that, we ask each node for the next
@ -360,46 +365,47 @@ the final inode is returned.
I also made two simple wrappers for this function:
INODE vfs_namei(const char *path)
{
return vfs_namei_mount(path, 0);
}
INODE vfs_mount(const char *path, INODE root)
{
return vfs_namei_mount(path, root);
}
{: .lang-c}
:::c
INODE vfs_namei(const char *path)
{
return vfs_namei_mount(path, 0);
}
INODE vfs_mount(const char *path, INODE root)
{
return vfs_namei_mount(path, root);
}
And finally, a function for unmounting file systems:
INODE vfs_umount(const char *path)
{
char *npath = strdup(path);
char *pth = &npath[1];
INODE ino = vfs_find_root(&pth);
if(!ino || pth)
{
free(npath);
return 0;
}
if(ino->child)
{
free(npath);
return 0;
} else {
// Remove node from mount tree
if(ino->parent->child == ino)
ino->parent->child = ino->older;
if(ino->younger)
ino->younger->older = ino->older;
if(ino->older)
ino->older->younger = ino->younger;
free(npath);
return ino;
}
}
:::c
INODE vfs_umount(const char *path)
{
char *npath = strdup(path);
char *pth = &npath[1];
INODE ino = vfs_find_root(&pth);
if(!ino || pth)
{
free(npath);
return 0;
}
if(ino->child)
{
free(npath);
return 0;
} else {
// Remove node from mount tree
if(ino->parent->child == ino)
ino->parent->child = ino->older;
if(ino->younger)
ino->younger->older = ino->older;
if(ino->older)
ino->older->younger = ino->younger;
free(npath);
return ino;
}
}
And that's it for now. A lot of code this time, but that's because I
don't want to push my changes to github quite yet, so I can't give you a

View File

@ -12,17 +12,17 @@ have some basic syscall interfaces to start from.
Newlib requires the following syscalls:
:::c
int close(int file)
int fstat(int file, struct stat *st)
int isatty(int file)
int link(char *old, char *new)
int lseek(int file, int ptr, int dir)
int open(const char *name, int flags, int mode)
int read(int file, char *ptr, int len)
int stat(const char *file, struct stat *st)
int unlink(char *name)
int write(int file, char *ptr, int len)
:::c
int close(int file)
int fstat(int file, struct stat *st)
int isatty(int file)
int link(char *old, char *new)
int lseek(int file, int ptr, int dir)
int open(const char *name, int flags, int mode)
int read(int file, char *ptr, int len)
int stat(const char *file, struct stat *st)
int unlink(char *name)
int write(int file, char *ptr, int len)
###open and close
Everything starts with `open`, so let's look at that first.
@ -30,79 +30,79 @@ Everything starts with `open`, so let's look at that first.
In order to keep track of the files that are opened by a process, we
need a new data structure, though; the _file descriptor_.
:::c
typedef struct
{
INODE ino;
uint32_t offset;
uint32_t flags;
uint32_t users;
} file_desc_t;
:::c
typedef struct
{
INODE ino;
uint32_t offset;
uint32_t flags;
uint32_t users;
} file_desc_t;
The file descriptor keeps track of our position in the file as well as
the mode it was opened in. File descriptors can also be shared between
processes (after a `fork()` for example), and it therefore has a use
counter. Two macros are used to manipulate the use counter
:::c
#define fd_get(fd) { (fd)->users++ }
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
:::c
#define fd_get(fd) { (fd)->users++ }
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
Each process descriptor has an array of pointers to file descriptors
:::c
file_desc_t *fd[NUM_FILEDES];
:::c
file_desc_t *fd[NUM_FILEDES];
`open` starts by finding a free file descriptor. It then finds the file,
opens the file and returns the index of the file descriptor it used:
:::c
int open(const char *name, int flags, int mode)
{
int fd;
// Find unused file descriptor
process_t *p = current->proc;
int i;
for(i=0; i < NUM_FILEDES; i++)
{
if(p->fd[i])
continue;
fd = i;
p->fd[fd] = calloc(1, sizeof(file_desc_t));
fd_get(p->fd[fd]);
break;
}
// Find file
INODE ino = vfs_namei(name);
// Open file
vfs_open(name, flags);
// Setup file descriptor
p->fd[fd]->ino = ino;
p->fd[fd]->offset = 0;
p->fd[fd]->flags = flags;
return fd;
}
:::c
int open(const char *name, int flags, int mode)
{
int fd;
// Find unused file descriptor
process_t *p = current->proc;
int i;
for(i=0; i < NUM_FILEDES; i++)
{
if(p->fd[i])
continue;
fd = i;
p->fd[fd] = calloc(1, sizeof(file_desc_t));
fd_get(p->fd[fd]);
break;
}
// Find file
INODE ino = vfs_namei(name);
// Open file
vfs_open(name, flags);
// Setup file descriptor
p->fd[fd]->ino = ino;
p->fd[fd]->offset = 0;
p->fd[fd]->flags = flags;
return fd;
}
I stripped away all of the sanity checking and error handling code here.
With that code, the function is more than twice as long.
`close` is even easier:
:::c
int close(int file)
{
int retval = vfs_close(p->fd[file]->ino);
if(!p->fd[file]->ino->parent)
free(p->fd[file]->ino);
fd_put(p->fd[file]);
p->fd[file] = 0;
return retval;
}
:::c
int close(int file)
{
int retval = vfs_close(p->fd[file]->ino);
if(!p->fd[file]->ino->parent)
free(p->fd[file]->ino);
fd_put(p->fd[file]);
p->fd[file] = 0;
return retval;
}
I always check if an inode has a parent before freeing it. If it has a
parent, it's part of the vfs mount tree, and should be kept around.
@ -112,83 +112,83 @@ Next, let's look at read.
It's actually really simple (excluding sanity checking and error
handling):
:::c
int read(int file, char *ptr, int len)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret;
return ret;
}
:::c
int read(int file, char *ptr, int len)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret;
return ret;
}
Write is pretty much the same:
:::c
int write(int file, char *ptr, int len)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret;
return ret;
}
:::c
int write(int file, char *ptr, int len)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret;
return ret;
}
###stat, fstat and isatty
`fstat` and `isatty` just passes on the information to the corresponding
vfs functions:
:::c
int fstat(int file, struct stat *st)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
return vfs_fstat(node, st);
}
:::c
int fstat(int file, struct stat *st)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
return vfs_fstat(node, st);
}
:::c
int isatty(int file)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
return vfs_isatty(node);
}
:::c
int isatty(int file)
{
process_t *p = current->proc;
INODE node = p->fd[file]->ino;
return vfs_isatty(node);
}
`stat` performs a `namei` lookup to get the node instead of taking it
from the process' file descriptor table.
:::c
int stat(const char *file, struct stat *st)
{
INODE node = vfs_namei(file);
int retval = vfs_fstat(node, st);
if(!node->parent)
free(node);
return retval;
}
:::c
int stat(const char *file, struct stat *st)
{
INODE node = vfs_namei(file);
int retval = vfs_fstat(node, st);
if(!node->parent)
free(node);
return retval;
}
###lseek
The final function I'll look at now is `lseek` which sets the current
position in the file:
:::c
int lseek(int file, int ptr, int dir)
{
process_t *p = current->proc;
if(dir == SEEK_SET)
{
p->fd[file]->offset = ptr;
}
if(dir == SEEK_CUR)
{
p->fd[file]->offset += ptr;
}
if(dir == SEEK_END)
{
p->fd[file]->offset = p->fd[file]->ino->length + ptr;
}
return p->fd[file]->offset;
}
:::c
int lseek(int file, int ptr, int dir)
{
process_t *p = current->proc;
if(dir == SEEK_SET)
{
p->fd[file]->offset = ptr;
}
if(dir == SEEK_CUR)
{
p->fd[file]->offset += ptr;
}
if(dir == SEEK_END)
{
p->fd[file]->offset = p->fd[file]->ino->length + ptr;
}
return p->fd[file]->offset;
}
I'll leave `link` and `unlink` for now, and come back to them when
I need them (i.e. I wish to implement a user writeable filesystem).

View File

@ -1,7 +1,7 @@
layout: post
title: "The debug file system"
subtitle: "It's still in the kernel!"
tags: [osdev]
tags: [osdev, filesystems]
This far in my vfs rewrite, I've made a [vfs mount tree](/blog/2013/12/Virtual-File-System2/) and written some [file operation syscalls](/blog/2013/12/VFS-syscalls/). Now it's time to actually use this, by writing a filesystem that can be mounted and then manipulated through the syscall interface.
@ -15,31 +15,34 @@ Three functions are needed for newlib `fprintf` to be able to write to
it: `stat`, `isatty` and `write`. Those are all rather simple, since
they won't need to keep track of which file we're referencing.
uint32_t debug_stat(INODE node, struct stat *st)
{
memset(st, 0, sizeof(struct stat));
st->st_mode = S_IFCHR;
return 0;
}
:::c
uint32_t debug_stat(INODE node, struct stat *st)
{
memset(st, 0, sizeof(struct stat));
st->st_mode = S_IFCHR;
return 0;
}
I don't care much about the stat for the debug file. Maybe I'll add some
creation time or so later...
uint32_t debug_isatty(INODE node)
{
return 1;
}
:::c
uint32_t debug_isatty(INODE node)
{
return 1;
}
The debug output is a terminal.
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset)
{
char *buf = calloc(size + 1, 1);
memcpy(buf, buffer, size);
kdbg_puts(buf);
free(buf);
return size;
}
:::c
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset)
{
char *buf = calloc(size + 1, 1);
memcpy(buf, buffer, size);
kdbg_puts(buf);
free(buf);
return size;
}
`kdbg_puts` is a function I wrote
[a long time ago](/blog/2012/06/Kernel-Debug-Functions/) which prints a
@ -48,48 +51,53 @@ data is null-terminated.
With this, I can define a driver for the "debug device":
vfs_driver_t debug_driver =
{
0, // open
0, // close
0, // read
debug_write, // write
0, // link
0, // unlink
debug_stat, // stat
debug_isatty, // isatty
0, // mkdir
0, // readdir
0 // finddir
};
:::c
vfs_driver_t debug_driver =
{
0, // open
0, // close
0, // read
debug_write, // write
0, // link
0, // unlink
debug_stat, // stat
debug_isatty, // isatty
0, // mkdir
0, // readdir
0 // finddir
};
And then write a function to setup the device:
INODE debug_dev_init()
{
INODE node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, "debug");
node->d = &debug_driver;
node->type = FS_CHARDEV;
return node;
}
:::c
INODE debug_dev_init()
{
INODE node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, "debug");
node->d = &debug_driver;
node->type = FS_CHARDEV;
return node;
}
Then, to activate it, all I need to do is add
vfs_init();
vfs_mount("/", debug_dev_init());
:::c
vfs_init();
vfs_mount("/", debug_dev_init());
in my kernel boot code. After that I can use the standard library
functions:
FILE *dbg = fopen("/", "w");
fprintf(dbg, "Hello, world!\n");
:::c
FILE *dbg = fopen("/", "w");
fprintf(dbg, "Hello, world!\n");
or even:
fopen("/", "r");
fopen("/", "w");
printf("Hello, world!\n");
:::c
fopen("/", "r");
fopen("/", "w");
printf("Hello, world!\n");
That's it for this time. Next time, we'll do some piping!

View File

@ -1,7 +1,7 @@
layout: post
title: "Pipes"
subtitle: "... and keyboard."
tags: [osdev]
tags: [osdev, filesystems]
In most unix-like systems, pipes can be used to make processes
communicate with each other. To the processes the pipe looks just like
@ -33,51 +33,52 @@ Consumer/Reader:
In code this translates to
uint32_t pipe_write(INODE ino, void *buffer, uint32_t size, uint32_t offset)
{
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
char *buf = (char *)buffer;
uint32_t bytes_written = 0;
while(bytes_written < size)
{
while((pipe->write_pos - pipe->read_pos) < pipe->size && bytes_written < size)
{
pipe->buffer[pipe->write_pos % pipe->size] = buf[bytes_written];
bytes_written++;
pipe->write_pos++;
}
scheduler_wake(&pipe->waiting);
if(bytes_written < size)
{
scheduler_sleep(current, &pipe->waiting);
schedule();
}
}
return bytes_written;
}
uint32_t pipe_read(INODE ino, void *buffer, uint32_t size, uint32_t offset)
{
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
char *buf = (char *)buffer;
uint32_t bytes_read = 0;
while(bytes_read == 0)
{
while((pipe->write_pos - pipe->read_pos) > 0 && bytes_read < size)
{
buf[bytes_read] = pipe->buffer[pipe->read_pos % pipe->size];
bytes_read++;
pipe->read_pos++;
}
scheduler_wake(&pipe->waiting);
if(bytes_read == 0)
{
scheduler_sleep(current, &pipe->waiting);
schedule();
}
}
return bytes_read;
}
:::c
uint32_t pipe_write(INODE ino, void *buffer, uint32_t size, uint32_t offset)
{
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
char *buf = (char *)buffer;
uint32_t bytes_written = 0;
while(bytes_written < size)
{
while((pipe->write_pos - pipe->read_pos) < pipe->size && bytes_written < size)
{
pipe->buffer[pipe->write_pos % pipe->size] = buf[bytes_written];
bytes_written++;
pipe->write_pos++;
}
scheduler_wake(&pipe->waiting);
if(bytes_written < size)
{
scheduler_sleep(current, &pipe->waiting);
schedule();
}
}
return bytes_written;
}
uint32_t pipe_read(INODE ino, void *buffer, uint32_t size, uint32_t offset)
{
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
char *buf = (char *)buffer;
uint32_t bytes_read = 0;
while(bytes_read == 0)
{
while((pipe->write_pos - pipe->read_pos) > 0 && bytes_read < size)
{
buf[bytes_read] = pipe->buffer[pipe->read_pos % pipe->size];
bytes_read++;
pipe->read_pos++;
}
scheduler_wake(&pipe->waiting);
if(bytes_read == 0)
{
scheduler_sleep(current, &pipe->waiting);
schedule();
}
}
return bytes_read;
}
Of course there should also be:
@ -95,87 +96,91 @@ two separate inodes - one that can be written to and one that can be
read from. The `data` field of the `vfs_node_st` struct of the two
inodes point to the same pipe struct.
typedef struct vfs_pipe
{
char *buffer;
uint32_t size;
uint32_t read_pos;
uint32_t write_pos;
uint32_t readers;
uint32_t writers;
semaphore_t semaphore;
list_head_t waiting;
} vfs_pipe_t;
:::c
typedef struct vfs_pipe
{
char *buffer;
uint32_t size;
uint32_t read_pos;
uint32_t write_pos;
uint32_t readers;
uint32_t writers;
semaphore_t semaphore;
list_head_t waiting;
} vfs_pipe_t;
The `readers` and `writers` fields are incremented or decremented when the read or
write end respectively are opened or closed... respectively...
Creating a new pipe (somewhat simplified):
uint32_t new_pipe(uint32_t size, INODE *nodes)
{
vfs_pipe_t *pipe = calloc(1, sizeof(vfs_pipe_t));
pipe->buffer = malloc(size);
pipe->size = size;
:::c
uint32_t new_pipe(uint32_t size, INODE *nodes)
{
vfs_pipe_t *pipe = calloc(1, sizeof(vfs_pipe_t));
pipe->buffer = malloc(size);
pipe->size = size;
nodes[0] = calloc(1, sizeof(vfs_node_t));
nodes[0]->d = &pipe_driver;
nodes[0]->data = pipe;
nodes[0]->flags = PIPE_READ;
nodes[0] = calloc(1, sizeof(vfs_node_t));
nodes[0]->d = &pipe_driver;
nodes[0]->data = pipe;
nodes[0]->flags = PIPE_READ;
nodes[1] = calloc(1, sizeof(vfs_node_t));
nodes[1]->d = &pipe_driver;
nodes[1]->data = pipe;
nodes[1]->flags = PIPE_WRITE;
nodes[1] = calloc(1, sizeof(vfs_node_t));
nodes[1]->d = &pipe_driver;
nodes[1]->data = pipe;
nodes[1]->flags = PIPE_WRITE;
return 0;
}
return 0;
}
###Using pipes
When starting up the keyboard driver, a pipe is created and connected to
`stdin` - the first file descriptor - of the current process.
void keyboard_init()
{
INODE tmp[2];
new_pipe(1024, tmp);
:::c
void keyboard_init()
{
INODE tmp[2];
new_pipe(1024, tmp);
keyboard_pipe = tmp[1];
vfs_open(keyboard_pipe, O_WRONLY);
keyboard_pipe = tmp[1];
vfs_open(keyboard_pipe, O_WRONLY);
process_t *p = current->proc;
p->fd[0] = calloc(1, sizeof(file_desc_t));
fd_get(p->fd[0]);
p->fd[0]->ino = tmp[0];
p->fd[0]->flags = O_RDONLY;
vfs_open(tmp[0], O_RDONLY);
process_t *p = current->proc;
p->fd[0] = calloc(1, sizeof(file_desc_t));
fd_get(p->fd[0]);
p->fd[0]->ino = tmp[0];
p->fd[0]->flags = O_RDONLY;
vfs_open(tmp[0], O_RDONLY);
register_int_handler(IRQ2INT(IRQ_KBD), keyboard_handler);
}
register_int_handler(IRQ2INT(IRQ_KBD), keyboard_handler);
}
The keyboard handler (based off
The keyboard handler (based off
[Brandon Friesen's tutorial](http://www.osdever.net/bkerndev/Docs/keyboard.htm)
writes each decoded key to the pipe:
registers_t *keyboard_handler(registers_t *r)
{
char code[2];
[...]
while(inb(KBD_STATUS_PORT) & 0x2);
scancode = inb(KBD_DATA_PORT);
code[0] = keyboard_decode(scancode);
code[1] = '\0';
if(code[0])
{
vfs_write(keyboard_pipe, (char *)code, 1, 0);
}
[...]
}
:::c
registers_t *keyboard_handler(registers_t *r)
{
char code[2];
[...]
while(inb(KBD_STATUS_PORT) & 0x2);
scancode = inb(KBD_DATA_PORT);
code[0] = keyboard_decode(scancode);
code[1] = '\0';
if(code[0])
{
vfs_write(keyboard_pipe, (char *)code, 1, 0);
}
[...]
}
At a later stage, I'll probably make the keyboard driver in the kernel
just pass the scancodes to the pipe and have the decoding done by

View File

@ -1,7 +1,7 @@
layout: post
title: "TAR Filesystem"
subtitle: "Almost a useful system"
tags: [osdev]
tags: [osdev, filesystems]
It's finally time to implement an actuall filesystem, and all the hard
work with the VFS framework will pay off.
@ -19,25 +19,26 @@ is preceded by a data block in human-readable format.
In the POSIX standard of 1988, the data block has the following format:
typedef struct
{
unsigned char name[100];
unsigned char mode[8];
unsigned char uid[8];
unsigned char gid[8];
unsigned char size[12];
unsigned char mtime[12];
unsigned char checksum[8];
unsigned char type[1];
unsigned char linkname[100];
unsigned char tar_indicator[6];
unsigned char tar_version[2];
unsigned char owner[32];
unsigned char group[32];
unsigned char device_major[8];
unsigned char device_minor[8];
unsigned char prefix[155];
}__attribute__((packed)) tar_header_t;
:::c
typedef struct
{
unsigned char name[100];
unsigned char mode[8];
unsigned char uid[8];
unsigned char gid[8];
unsigned char size[12];
unsigned char mtime[12];
unsigned char checksum[8];
unsigned char type[1];
unsigned char linkname[100];
unsigned char tar_indicator[6];
unsigned char tar_version[2];
unsigned char owner[32];
unsigned char group[32];
unsigned char device_major[8];
unsigned char device_minor[8];
unsigned char prefix[155];
}__attribute__((packed)) tar_header_t;
The data block is 500 bytes long, but the data starts 512 (one standard
disk sector) bytes after the start of the data block
@ -56,52 +57,53 @@ are loaded into ram and you might want to access them randomly.
Instead, I reshape the file list into a file tree:
tree_t *build_tar_tree(tar_header_t *tar)
{
...
while(tar->name[0])
{
tartree_add_node(tree, tar, (char *)&tar->name);
uint32_t size;
sscanf((char *)&tar->size, "%o", &size);
tar = (tar_header_t *)((size_t)tar + size + 512);
if((size_t)tar % 512)
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%512))
}
...
}
void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
{
...
tree_node_t *node = tree->root;
char *p;
for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
{
int found = 0;
list_t *l;
for_each_in_list(&node->children)
{
...
if(!strcmp(entry->name, p))
{
found = 1;
node = tn;
break;
}
}
if(!found)
{
...
tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
n->name = strdup(p);
n->tar = tar;
...
tree_make_child(node, new);
node = new;
}
}
}
:::c
tree_t *build_tar_tree(tar_header_t *tar)
{
...
while(tar->name[0])
{
tartree_add_node(tree, tar, (char *)&tar->name);
uint32_t size;
sscanf((char *)&tar->size, "%o", &size);
tar = (tar_header_t *)((size_t)tar + size + 512);
if((size_t)tar % 512)
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%512))
}
...
}
void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
{
...
tree_node_t *node = tree->root;
char *p;
for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
{
int found = 0;
list_t *l;
for_each_in_list(&node->children)
{
...
if(!strcmp(entry->name, p))
{
found = 1;
node = tn;
break;
}
}
if(!found)
{
...
tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
n->name = strdup(p);
n->tar = tar;
...
tree_make_child(node, new);
node = new;
}
}
}
Note that this assumes that the files and directories of the tar archive
are in top-down order, e.g. that `/bin` is before `/bin/echo`, otherwise
@ -112,24 +114,26 @@ strange.
The tarfs driver makes use of the data field in the vfs node to store
the tar tree.
INODE tarfs_init(tar_header_t *tar)
{
vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, "tarfs");
node->d = &tarfs_driver;
node->type = FS_DIRECTORY;
tree_t *tar_tree = build_tar_tree(tar);
node->data = tar_tree->root;
free(tar_tree);
return node;
}
:::c
INODE tarfs_init(tar_header_t *tar)
{
vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, "tarfs");
node->d = &tarfs_driver;
node->type = FS_DIRECTORY;
tree_t *tar_tree = build_tar_tree(tar);
node->data = tar_tree->root;
free(tar_tree);
return node;
}
I then add the following to my kernel initialization function:
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start);
vfs_mount("/", tarfs_init(tarfs_location));
:::c
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start);
vfs_mount("/", tarfs_init(tarfs_location));
where `mods` is the multiboot modules table as loaded by grub.
@ -137,30 +141,31 @@ where `mods` is the multiboot modules table as loaded by grub.
Finally, the tarfs driver functions. For now I only need to implement
`read()` and `finddir()`.
INODE tar_finddir(INODE dir, const char *name)
{
tree_node_t *tn = (tree_node_t *)dir->data;
list_t *l;
for_each_in_list(&tn->children, l)
{
tree_node_t *cn = list_entry(l, tree_node_t, siblings);
...
if(!strcmp(entry->name, name)
{
INODE node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, entry->name);
node->d = &tarfs_driver;
node->data = (void *)cn;
sscanf((char *)&entry->tar->size, "%o", &node->length);
if(entry->tar->type[0] == TAR_TYPE_DIR)
node->type = FS_DIRECTORY;
else
node->type = FS_FILE;
return node;
}
}
return 0;
}
:::c
INODE tar_finddir(INODE dir, const char *name)
{
tree_node_t *tn = (tree_node_t *)dir->data;
list_t *l;
for_each_in_list(&tn->children, l)
{
tree_node_t *cn = list_entry(l, tree_node_t, siblings);
...
if(!strcmp(entry->name, name)
{
INODE node = calloc(1, sizeof(vfs_node_t));
strcpy(node->name, entry->name);
node->d = &tarfs_driver;
node->data = (void *)cn;
sscanf((char *)&entry->tar->size, "%o", &node->length);
if(entry->tar->type[0] == TAR_TYPE_DIR)
node->type = FS_DIRECTORY;
else
node->type = FS_FILE;
return node;
}
}
return 0;
}
`Finddir` allocates space for a new inode for each file that is searched
for. It's up to the caller to free the inode when it's done with it.
@ -170,39 +175,42 @@ it at a few places... Someday I will clean up all my memory leaks.
Anyway, `finddir` also finds the right node in the tarfs tree and puts
it in the `data` field of the inode.
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset)
{
tree_node_t *tn = (tree_node_t *)node->data;
tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
tar_header_t *tar = te->tar;
uint32_t tsz;
sscanf((char *)&tar->size, "%o", &tsz);
if(offset > tsz) return EOF;
if((size + offset) > tsz) size = tsz - offset;
offset = offset + (uint32_t)tar + 512;
memcpy(buffer, (void *)offset, size);
if(size == tsz - offset)
((char *)buffer)[size] = EOF;
return size;
}
:::c
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset)
{
tree_node_t *tn = (tree_node_t *)node->data;
tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
tar_header_t *tar = te->tar;
uint32_t tsz;
sscanf((char *)&tar->size, "%o", &tsz);
if(offset > tsz) return EOF;
if((size + offset) > tsz) size = tsz - offset;
offset = offset + (uint32_t)tar + 512;
memcpy(buffer, (void *)offset, size);
if(size == tsz - offset)
((char *)buffer)[size] = EOF;
return size;
}
###Using it
Now, all I need to do in order to make read-only files accessible to my
kernel is put them in a directory and run
$ tar -cf tarfs.tar tarfs/*
:::bash
$ tar -cf tarfs.tar tarfs/*
and then make sure `tarfs.tar` is loaded as a multiboot module by qemu
$ qemu-system-i386 -kernel kernel -initrd tarfs.tar
:::bash
$ qemu-system-i386 -kernel kernel -initrd tarfs.tar
or by adding a line to the grub `menu.lst` file.
module /boot/tarfs.tar
module /boot/tarfs.tar
###Final note
While writing this post, I got back to polishing this code and added

View File

@ -5,17 +5,18 @@ tags: [osdev]
I've described my [syscall interface](/blog/2013/06/System-Calls/) previously. I've also described
the [file-related syscalls](/blog/2013/12/VFS-syscalls/). In order to build [newlib](http://wiki.osdev.org/Porting_Newlib), some more
syscalls are required.
syscalls are required.
Those are:
void *sbrk(int incr);
int getpid();
int fork();
void _exit(int rc);
int wait(int *status);
int kill(int pid, int sig);
int execve(char *name, char **argv, char **env);
:::c
void *sbrk(int incr);
int getpid();
int fork();
void _exit(int rc);
int wait(int *status);
int kill(int pid, int sig);
int execve(char *name, char **argv, char **env);
Let's just go through them one at a time:
@ -27,51 +28,53 @@ The user space one makes use of the [process memory
manager](/blog/2013/06/Even-More-Memory/) to return
a chunk of new memory for the `malloc` functions.
void *usr_sbrk(int incr)
{
process_t *p = current->proc;
mem_area_t *area = find_including(p, p->mm.data_end);
if(area)
{
if(area->end > (p->mm.data_end + incr))
{
// The current memory area is large enough
} else {
// Increase memory area
new_area(p, area->end, p->mm.data_end + incr, \
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
MM_TYPE_DATA);
}
} else {
// Create a new memory area
new-area(p, p->mm.data_end, p->mm.data_end + incr, \
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
MM_TYPE_DATA);
}
p->mm.data_end = p->mm.data_end + incr;
return (void *)(p->mm.data_end - incr);
}
:::c
void *usr_sbrk(int incr)
{
process_t *p = current->proc;
mem_area_t *area = find_including(p, p->mm.data_end);
if(area)
{
if(area->end > (p->mm.data_end + incr))
{
// The current memory area is large enough
} else {
// Increase memory area
new_area(p, area->end, p->mm.data_end + incr, \
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
MM_TYPE_DATA);
}
} else {
// Create a new memory area
new-area(p, p->mm.data_end, p->mm.data_end + incr, \
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
MM_TYPE_DATA);
}
p->mm.data_end = p->mm.data_end + incr;
return (void *)(p->mm.data_end - incr);
}
The kernel space version is just a simple linear allocator
uintptr_t kmem_top = KERNEL_HEAP_START;
uintptr_t kmem_ptr = KERNEL_HEAP_START;
void *sbrk(int incr)
{
if(kmem_ptr + incr > KERNEL_HEAP_END)
{
// PANIC!
...
}
while(kmem_top < kmem_ptr + incr)
{
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
PAGE_PRESENT | PAGE_WRITE));
kmem_top += PAGE_SIZE;
}
kmem_ptr = kmem_ptr + incr;
return (void *)kmem_ptr - incr;
}
:::c
uintptr_t kmem_top = KERNEL_HEAP_START;
uintptr_t kmem_ptr = KERNEL_HEAP_START;
void *sbrk(int incr)
{
if(kmem_ptr + incr > KERNEL_HEAP_END)
{
// PANIC!
...
}
while(kmem_top < kmem_ptr + incr)
{
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
PAGE_PRESENT | PAGE_WRITE));
kmem_top += PAGE_SIZE;
}
kmem_ptr = kmem_ptr + incr;
return (void *)kmem_ptr - incr;
}
Hopefully it's obvious why the kernel one is called `sbrk` while the
user one has a different name.
@ -79,42 +82,45 @@ user one has a different name.
###getpid
`getpid` is rather obvious:
int getpid()
{
return current->proc->pid;
}
:::c
int getpid()
{
return current->proc->pid;
}
###fork
`fork` clones the current process and starts a new thread of execution.
int fork()
{
process_t *child = fork_process();
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
ch_thread->r.eax = 0;
scheduler_insert(ch_thread);
return child->pid;
}
:::c
int fork()
{
process_t *child = fork_process();
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
ch_thread->r.eax = 0;
scheduler_insert(ch_thread);
return child->pid;
}
###_exit
`_exit` stops a program and wakes up any processes that are sleeping on
it.
void _exit(int rc)
{
process_t *p = current->proc;
// Close all open files
int i;
for(i = 0; i < NUM_FILEDES; i++)
{
if(p->fd[i])
close(i);
}
exit_process(current->proc, rc);
current->state = THREAD_STATE_FINISHED;
schedule();
}
:::c
void _exit(int rc)
{
process_t *p = current->proc;
// Close all open files
int i;
for(i = 0; i < NUM_FILEDES; i++)
{
if(p->fd[i])
close(i);
}
exit_process(current->proc, rc);
current->state = THREAD_STATE_FINISHED;
schedule();
}
`_exit` doesn't return, and in fact `schedule()` will never return as
far as this thread is concerned.
@ -125,18 +131,19 @@ its parent process has executed a `wait` syscall.
Actually, I didn't quite implement `wait` yet, but instead use
a `waitpid` for now, which is a bit more specific:
int waitpid(int pid)
{
process_t *proc = get_process(pid);
while(proc->state != PROC_STATE_FINISHED)
{
scheduler_sleep(current, &proc->waiting);
schedule();
}
int ret = proc->exit_code;
free_process(proc);
return ret;
}
:::c
int waitpid(int pid)
{
process_t *proc = get_process(pid);
while(proc->state != PROC_STATE_FINISHED)
{
scheduler_sleep(current, &proc->waiting);
schedule();
}
int ret = proc->exit_code;
free_process(proc);
return ret;
}
This _should_ contain a check that process `pid` is a child of the
current process too...
@ -162,104 +169,111 @@ is:
First of all, the executable is found. If it doesn't exist, we want to
fail as early as possible - before we destroy everything.
int execve(char *name, char **argv, char **env)
{
INODE executable = vfs_namei(name);
if(!executable)
{
errno = ENOENT;
return -1;
}
...
:::c
int execve(char *name, char **argv, char **env)
{
INODE executable = vfs_namei(name);
if(!executable)
{
errno = ENOENT;
return -1;
}
...
The arguments and environment are null-terminated lists of strings
stored in user space, so they have to be copied into kernel space before
the user space is destroyed:
...
usigned int envc = 0;
char **temp_env = 0;
if(env)
{
while(env[envc++]); // Count number of environmental variables
temp_env = calloc(envc, sizeof(char *));
unsigned int i = 0;
while(env[i])
{
temp_env[i] = strdup(env[i]);
i++;
}
}
// Do the same thing for argv
...
:::c
...
usigned int envc = 0;
char **temp_env = 0;
if(env)
{
while(env[envc++]); // Count number of environmental variables
temp_env = calloc(envc, sizeof(char *));
unsigned int i = 0;
while(env[i])
{
temp_env[i] = strdup(env[i]);
i++;
}
}
// Do the same thing for argv
...
Next, Delete all memory from the previous executable and [load the new
one](/blog/2013/08/Loading-Elf/):
procmm_removeall(current->proc);
load_elf(executable);
current->r.eax = current->r.ebx = current->r.ecx = \
current->r.edx = 0;
:::c
procmm_removeall(current->proc);
load_elf(executable);
current->r.eax = current->r.ebx = current->r.ecx = \
current->r.edx = 0;
We need to put the arguments and environment back into the new
executable's user space, so a new stack area is created:
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
MM_TYPE_STACK);
current->kernel_thread = (registers_t *)current;
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
:::c
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
MM_TYPE_STACK);
current->kernel_thread = (registers_t *)current;
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
Then, copy the environment and arguments onto the stack:
if(env)
{
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
env = (char **)pos;
int i = 0;
while(temp_env[i])
{
pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
env[i] = (char *)pos;
i++;
}
env[envc-1] = 0;
}
// Do the same for argc
...
:::c
if(env)
{
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
env = (char **)pos;
int i = 0;
while(temp_env[i])
{
pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
env[i] = (char *)pos;
i++;
}
env[envc-1] = 0;
}
// Do the same for argc
...
And finally, push the argument count, argument list and environment list
onto the stack:
pos = pos - 3;
pos[0] = (uint32_t)argc - 1;
pos[1] = (uint32_t)argv;
pos[2] = (uint32_t)env;
current->r.useresp = current->r.ebp = (uint32_t)pos;
current->r.ecx = (uint32_t)pos;
return 0;
}
:::c
pos = pos - 3;
pos[0] = (uint32_t)argc - 1;
pos[1] = (uint32_t)argv;
pos[2] = (uint32_t)env;
current->r.useresp = current->r.ebp = (uint32_t)pos;
current->r.ecx = (uint32_t)pos;
return 0;
}
This pushes argc, argv and env as arguments to the executabl. We can
use this to set up the `environ` variable of newlib. The crt0 in newlib
pushes `ecx` to the stack and then calls `_init` which looks like this:
extern char **environ;
void _init(uint32_t *args)
{
int argc;
char **argv;
if(args)
{
argc = args[0];
argv = (char **)args[1];
environ = (char **)args[2];
} else {...}
exit(main(argc, argv));
}
:::c
extern char **environ;
void _init(uint32_t *args)
{
int argc;
char **argv;
if(args)
{
argc = args[0];
argv = (char **)args[1];
environ = (char **)args[2];
} else {...}
exit(main(argc, argv));
}

View File

@ -17,15 +17,17 @@ I'll just look at `signal` and `kill` and ignore things like
First of all, in order to compile newlib with kernel supported signals,
you need the line
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
:::make
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
in your entry in `newlib/configure.host` as described in [the osdev
wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
Then you need the syscalls:
sig_t signal(int signum, sig_t handler);
int kill(int pid, int sig);
:::c
sig_t signal(int signum, sig_t handler);
int kill(int pid, int sig);
###Raising signals
@ -38,31 +40,33 @@ as I go along later.
The way I chose to implement signals was through a list for each
process. When a signal is sent using `kill` or similar, a `signal_t`
typedef struct
{
uint32_t sig;
uint32_t sender;
list_head_t queue;
} signal_t;
:::c
typedef struct
{
uint32_t sig;
uint32_t sender;
list_head_t queue;
} signal_t;
is created and added to the processes list:
int signal_process(int pid, int signum)
{
process_t *p = get_process(pid);
...
signal_t *signal = calloc(1, sizeof(signal_t));
signal->sig = signum;
signal->sender = current->proc->pid;
init_list(signal->queue);
append_to_list(p->signal_queue, signal->queue);
:::c
int signal_process(int pid, int signum)
{
process_t *p = get_process(pid);
...
signal_t *signal = calloc(1, sizeof(signal_t));
signal->sig = signum;
signal->sender = current->proc->pid;
init_list(signal->queue);
append_to_list(p->signal_queue, signal->queue);
if(p == current->proc)
{
handle_signals(current);
}
return 0;
}
if(p == current->proc)
{
handle_signals(current);
}
return 0;
}
If the currently running thread is in the process being signalled, we
handle the signals immediately. Otherwise it can wait for a bit.
@ -75,13 +79,14 @@ The `signal` syscall lets the process select how to handle a certain
signal. Each process also contains a table of `sig_t` and the `signal`
syscall calls the following function:
sig_t switch_handler(int signum, sig_t handler)
{
...
sig_t old = current->proc->signal_handler[signum];
current->proc->signal_handler[signum] = handler;
return old;
}
:::c
sig_t switch_handler(int signum, sig_t handler)
{
...
sig_t old = current->proc->signal_handler[signum];
current->proc->signal_handler[signum] = handler;
return old;
}
The cut out part of this function contains code to make sure the handler
of signal 9 (`SIGKILL`) is never changed from `SIG_DFL`.
@ -94,24 +99,25 @@ as the process continues running should be soon enough.
I chose to hook into the my interrupt handler:
registers_t *idt_handler(registers_t *r)
{
...
if(int_handlers[r->int_no)
{
...
registers_t *ret = int_handlers[r->int_no](r);
if((ret->cs & 0x3) == 0x3)
{
ret = (registers_t *)handle_signals((thread_t *)ret);
...
}
...
return ret
}
...
}
:::c
registers_t *idt_handler(registers_t *r)
{
...
if(int_handlers[r->int_no)
{
...
registers_t *ret = int_handlers[r->int_no](r);
if((ret->cs & 0x3) == 0x3)
{
ret = (registers_t *)handle_signals((thread_t *)ret);
...
}
...
return ret
}
...
}
So, what does `handle_signals` actually do?
@ -125,43 +131,45 @@ If the handler is set to `SIG_DFL` the function looks up the correct
default signal handler in a table and calls it. It may be one of the
following:
void sighandler_ignore(int num)
{
(void)num;
}
void sighandler_terminate(int num)
{
fprintf(stderr,, "Process %x terminated by signal %x\n", \
current->proc->pid, num);
_exit(num);
}
void sighandler_coredump(int num)
{ /* Same as above */ }
void sighandler_stop(int num)
{ /* Not implemented yet */ }
void sighandler_continue(int num)
{ /* Not implemented yet */ }
:::c
void sighandler_ignore(int num)
{
(void)num;
}
void sighandler_terminate(int num)
{
fprintf(stderr,, "Process %x terminated by signal %x\n", \
current->proc->pid, num);
_exit(num);
}
void sighandler_coredump(int num)
{ /* Same as above */ }
void sighandler_stop(int num)
{ /* Not implemented yet */ }
void sighandler_continue(int num)
{ /* Not implemented yet */ }
If the handler is a function a new thread is created to handle it:
...
sig_t handler = th->proc->signal_handler[signal->sig];
thread_t *h = new_thread((void (*)(void))handler, 1);
append_to_list(th->proc->threads, h->process_threads);
h->proc = th->proc;
uint32_t *stack = ((uint32_t *)th->r.useresp;
*--stack = signal->sig;
*--stack = SIGNAL_RETURN_ADDRESS;
h->r.useresp = h->r.ebp = (uint32_t)stack;
remove_from_list(signal->queue);
free(signal);
scheduler_remove(h);
scheduler_sleep(th, &h->waiting);
scheduler_cheat(h);
schedule();
}
:::c
...
sig_t handler = th->proc->signal_handler[signal->sig];
thread_t *h = new_thread((void (*)(void))handler, 1);
append_to_list(th->proc->threads, h->process_threads);
h->proc = th->proc;
uint32_t *stack = ((uint32_t *)th->r.useresp;
*--stack = signal->sig;
*--stack = SIGNAL_RETURN_ADDRESS;
h->r.useresp = h->r.ebp = (uint32_t)stack;
remove_from_list(signal->queue);
free(signal);
scheduler_remove(h);
scheduler_sleep(th, &h->waiting);
scheduler_cheat(h);
schedule();
}
This creates a new thread and pushes the signal number (as an argument)
and a return address to the threads stack. It then places the new thread

View File

@ -25,7 +25,8 @@ I've described how I use tmux with qemu, gdb and telnet
pane which displays the debug information, but one thing at a time.
Here's what I did in a bash script:
tmux split-window -h 'qemu-system-i386 -kernel build/kernel/kernel -initrd "build/tarfs.tar" -curses -monitor telnet:localhost:4444,server -s -S -serial file:serial.out'
:::bash
tmux split-window -h 'qemu-system-i386 -kernel build/kernel/kernel -initrd "build/tarfs.tar" -curses -monitor telnet:localhost:4444,server -s -S -serial file:serial.out'
This opens a new tmux pane with qemu running my kernel and loading
`tarfs.tar` as a multiboot module. The `-monitor` flag starts a telnet
@ -36,34 +37,37 @@ from serial port 1 to a file.
The next step is:
tmux split-window -v 'tail -f serial.out | util/colorize.sh'
:::bash
tmux split-window -v 'tail -f serial.out | util/colorize.sh'
which opens up another tmux pane that displays the serial output using
the auto-updating feature of `tail`. The script `colorize.sh` colorizes
certain words of the output:
#!/usr/bin/env bash
C_NO=`echo -e "\\033[0m"`
C_RED=`echo -e "\\033[31m"`
C_GREEN=`echo -e "\\033[32m"`
C_YELLOW=`echo -e "\\033[33m"`
C_BLUE=`echo -e "\\033[36m"`
while read line; do
echo $line | sed \
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
done
:::bash
#!/usr/bin/env bash
C_NO=`echo -e "\\033[0m"`
C_RED=`echo -e "\\033[31m"`
C_GREEN=`echo -e "\\033[32m"`
C_YELLOW=`echo -e "\\033[33m"`
C_BLUE=`echo -e "\\033[36m"`
while read line; do
echo $line | sed \
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
done
Next is
tmux select-pane -L
tmux split-window -v 'i586-elf-gdb'
tmux select-pane -U
telnet localhost 4444
:::bash
tmux select-pane -L
tmux split-window -v 'i586-elf-gdb'
tmux select-pane -U
telnet localhost 4444
Which opens a new pane for the `gdb` debugger and then moves back to the
first pane to open the telnet terminal.
@ -76,38 +80,41 @@ Printing](/media/img/debug_print.png)](/media/img/debug_print_full.png)
Notice the yellow lines in that screenshot?
The ones that say
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
(HEAD, harddrive): Ext2 unlink function. Untested.
Kernel compilation: Mar 7 2014 14:06:27
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
(HEAD, harddrive): Ext2 unlink function. Untested.
Kernel compilation: Mar 7 2014 14:06:27
The data for this is stored in a special file called `version.c`
#include <version.h>
char * __kernel_git_hash = GITHASH;
char * __kernel_git_date = GITDATE;
int __kernel_git_dirty = GITDIRTY;
char * __kernel_git_message = GITMESSAGE;
char * __kernel_git_branch = GITBRANCH;
char * __kernel_build_date = __DATE__;
char * __kernel_build_time = __TIME__;
:::c
#include <version.h>
char * __kernel_git_hash = GITHASH;
char * __kernel_git_date = GITDATE;
int __kernel_git_dirty = GITDIRTY;
char * __kernel_git_message = GITMESSAGE;
char * __kernel_git_branch = GITBRANCH;
char * __kernel_build_date = __DATE__;
char * __kernel_build_time = __TIME__;
which has a special line in the kernel makefile:
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")'
:::make
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")'
A few more lines makes sure `version.c` is always recompiled if any
other part of the kernel is:
kernel: $(kernel_objects)
rm -f version.o
$(MAKE) version.o
$(LINK)
:::make
kernel: $(kernel_objects)
rm -f version.o
$(MAKE) version.o
$(LINK)
Obviously, this is a bit simplified. But not much. I might make a post
about my makefiles some day...

View File

@ -1,7 +1,7 @@
layout: post
title: "DITo - Framework"
subtitle: "the Disk Image TOols"
tags: [osdev]
tags: [osdev, filesystems]
In my osdeving, I was starting to reach the point where a disk driver
seemed like the obvious next step. This was pretty much entirely unknown
@ -42,33 +42,36 @@ some parts from scratch. Let's go!
The basic operations of DITo are reading from or writing to image files
or disk drives. Each drive type has a driver
typedef struct drive_driver
{
int (*open)(struct drive_t *d, int flags);
int (*close)(struct drive_t *d, int flags);
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset);
} drive_driver_t;
:::c
typedef struct drive_driver
{
int (*open)(struct drive_t *d, int flags);
int (*close)(struct drive_t *d, int flags);
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset);
} drive_driver_t;
The drive type contains a pointer to the driver and a pointer to some
arbitrary data used by the driver.
typedef struct drive_t
{
struct drive_driver *d;
void *data;
} drive_t;
:::c
typedef struct drive_t
{
struct drive_driver *d;
void *data;
} drive_t;
Then there are some wrapper functions for performing the required
operations:
int drive_open(struct drive_t *d, int flags)
{
if(d->d->open)
return d->d->open(d, flags);
else
return 0;
}
:::c
int drive_open(struct drive_t *d, int flags)
{
if(d->d->open)
return d->d->open(d, flags);
else
return 0;
}
and simmilar for `drive_close`, `drive_read` and `drive_write`.
@ -78,30 +81,32 @@ The next important part of DITo is the filesystem handling. After
thinking about it, the important primitive functions for all file
operations I could think about are all in a filesystem driver struct:
struct fs_driver
{
INODE (*open)(struct fs_t *fs, const char *path, int flags);
int (*close)(struct fs_t *fs, INODE ino);
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
int (*truncate)(struct fs_t *fs, INODE ino, off_t length);
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st);
int (*touch)(struct fs_t *fs, const char *path, struct stat *st);
int (*link)(struct fs_t *fs, const char *path1, const char *path2);
int (*unlink)(struct fs_t *fs, const char *path);
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num);
};
:::c
struct fs_driver
{
INODE (*open)(struct fs_t *fs, const char *path, int flags);
int (*close)(struct fs_t *fs, INODE ino);
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
int (*truncate)(struct fs_t *fs, INODE ino, off_t length);
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st);
int (*touch)(struct fs_t *fs, const char *path, struct stat *st);
int (*link)(struct fs_t *fs, const char *path1, const char *path2);
int (*unlink)(struct fs_t *fs, const char *path);
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num);
};
The `fs_t` type contains a pointer to the driver, a pointer to the drive
and a general data pointer.
typedef struct fs_t
{
struct fs_driver *driver;
drive_t *d;
void *data;
} fs_t;
:::c
typedef struct fs_t
{
struct fs_driver *driver;
drive_t *d;
void *data;
} fs_t;
The wrapper functions `fs_open`, `fs_close` and so on work the same way
as the `drive_*` functions.
@ -109,14 +114,15 @@ as the `drive_*` functions.
The `INODE` type is a pointer to a struct containing a pointer to the
filesystem, a unique inode number and a pointer to arbitrary data.
struct ino_st
{
fs_t *fs;
unsigned int ino;
void *data;
};
typedef struct ino_st * INODE;
:::c
struct ino_st
{
fs_t *fs;
unsigned int ino;
void *data;
};
typedef struct ino_st * INODE;
And that's the basic framework. As you probably notice, the same `fs_t`
pointer is passed to most functions twice. Once as `fs` and once as
@ -129,41 +135,43 @@ if I have one image of an FAT floppy disk with a file I want copied to
the ext2 formated second partition of a hard drive image, I could do
someting like this:
drive_t *fat_disk = image_drive("floppy.img");
drive_open(fat_disk, READ_FLAG);
drive_t *ext2_disk = image_drive("harddrive.img");
drive_open(ext2_disk, READ_WRITE_FLAG);
drive_t *ext2_partition = mbr_drive(ext2_disk, 2);
drive_open(ext2_partition, READ_WRITE_FLAG);
fs_t *fat = fat_fs(fat_disk);
fs_t *ext2 = ext2_fs(ext2_partition);
INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
struct st *st = malloc(sizeof(struct st));
fs_struct(fat, source, st);
fs_touch(ext2, "/new/path", st);
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
void *buffer = malloc(BUFER_SIZE);
off_t offset = 0;
off_t add = 0;
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset))
{
fs_write(ext2, destination, buffer, BUFFER_SIZE, offset);
offset += add;
}
fs_close(destination);
fs_close(source);
drive_close(ext2_partition);
drive_close(fat_disk);
:::c
drive_t *fat_disk = image_drive("floppy.img");
drive_open(fat_disk, READ_FLAG);
drive_t *ext2_disk = image_drive("harddrive.img");
drive_open(ext2_disk, READ_WRITE_FLAG);
drive_t *ext2_partition = mbr_drive(ext2_disk, 2);
drive_open(ext2_partition, READ_WRITE_FLAG);
fs_t *fat = fat_fs(fat_disk);
fs_t *ext2 = ext2_fs(ext2_partition);
INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
struct st *st = malloc(sizeof(struct st));
fs_struct(fat, source, st);
fs_touch(ext2, "/new/path", st);
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
void *buffer = malloc(BUFER_SIZE);
off_t offset = 0;
off_t add = 0;
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset))
{
fs_write(ext2, destination, buffer, BUFFER_SIZE, offset);
offset += add;
}
fs_close(destination);
fs_close(source);
drive_close(ext2_partition);
drive_close(fat_disk);
Which of couse will eventually become its own tool so that the actual
work the end user has to do becomes:
$ dito-cp floppy.img:/path/to/file harddrive.img:2:/new/path
:::bash
$ dito-cp floppy.img:/path/to/file harddrive.img:2:/new/path

View File

@ -1,7 +1,7 @@
layout: post
title: "DITo - Drives"
subtitle: "Exploring the MBR"
tags: [osdev]
tags: [osdev, filesystems]
Let's write a few drive drivers.
@ -15,61 +15,64 @@ First of all, let's think of what data we'd need to store. The filename
might be nice to have, and probably a pointer to the opened file. Would
hate to lose that...
struct im_data
{
char *filename;
FILE *file;
};
:::c
struct im_data
{
char *filename;
FILE *file;
};
The functions required for the driver would then make use of the
c standard library:
int im_open(drive_t *d, int flags)
{
struct im_data *data = d->data;
data->file = fopen(data->filename, flags);
return 1;
}
int im_close(drive_t *d, int flags)
{
struct im_data *data = d->data;
fclose(data->file);
return 1;
}
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
{
struct im_data *data = d->data;
fseek(data->file, offset, SEEK_SET);
return fread(buffer, length, 1, data->file);
}
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
{
struct im_data *data = d->data;
fseek(data->file, offset, SEEK_SET);
return fwrite(buffer, length, 1, data->file);
}
:::c
int im_open(drive_t *d, int flags)
{
struct im_data *data = d->data;
data->file = fopen(data->filename, flags);
return 1;
}
int im_close(drive_t *d, int flags)
{
struct im_data *data = d->data;
fclose(data->file);
return 1;
}
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
{
struct im_data *data = d->data;
fseek(data->file, offset, SEEK_SET);
return fread(buffer, length, 1, data->file);
}
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
{
struct im_data *data = d->data;
fseek(data->file, offset, SEEK_SET);
return fwrite(buffer, length, 1, data->file);
}
The function for setting up the drive is equally simple:
drive_driver_t im_driver =
{
im_open,
im_close,
im_read,
im_write,
};
drive_t *image_drive(const char *filename)
{
drive_t *d = calloc(1, sizeof(drive_t));
struct im_data *data = calloc(1, sizeof(struct im_data));
d->driver = &im_driver;
d->data = data;
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
return d;
}
:::c
drive_driver_t im_driver =
{
im_open,
im_close,
im_read,
im_write,
};
drive_t *image_drive(const char *filename)
{
drive_t *d = calloc(1, sizeof(drive_t));
struct im_data *data = calloc(1, sizeof(struct im_data));
d->driver = &im_driver;
d->data = data;
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
return d;
}
As always, I've stripped away all sanity checking and error handling.
@ -85,15 +88,16 @@ The MBR starts with 436 bytes of space for a bootloader, then there's a
The entries of the table has the following structure:
struct MBR
{
uint8_t boot_indicator;
uint8_t start_chs[3];
uint8_t system_id;
uint8_t end_chs[3];
uint32_t start_lba;
uint32_t num_sectors;
}__attribute__((packed));
:::c
struct MBR
{
uint8_t boot_indicator;
uint8_t start_chs[3];
uint8_t system_id;
uint8_t end_chs[3];
uint32_t start_lba;
uint32_t num_sectors;
}__attribute__((packed));
_CHS_ and _LBA_ are different ways of addressing sectors of the disk.

View File

@ -1,8 +1,8 @@
layout: post
title: "DITo - Ext2"
subtitle: "A beginning..."
tags: [osdev]
tags: [osdev, filesystems]
It's finally time to start looking at an actual filesystem.
I chose to begin with ext2.
I chose to begin with ext2.

View File

@ -3,7 +3,7 @@ permalink: /about/
Thomas Lovén
------------
<img src="/media/img/thomas.png" width="300" class="right">
![Me](/media/img/thomas.png){: width="300" .right}
Born in Karlskrona in the south of Sweden. Lived there for 18 years
before doing army service in Eksjö and then moving to Gothenburg for

View File

@ -10,37 +10,36 @@ Translation and grades can be provided on request.
I earned my bachelors degree in 2012.
TDA305 Datorintroduktion 1,5hp
ESS115 Elektriska nät och system 7,5hp
EEF031 Elektromagnetisk fältteori 7,5hp
TIF080 Experimentell fysik 1 - mätteknik 9hp
TIF090 Experimentell fysik 2 - bas 4,5hp
FFY011 Fasta tillståndets fysik 7,5hp
MVE035 Flervariabelanalys 6hp
MVE030 Fourieranalys 6hp
TIF010 Fysiken omkring oss 7,5hp
TMA970 Inledande matematisk analys 6hp
FKA150 Inledande teknisk kommunikation 1,5hp
TIFX02 Kandidatarbete vid Teknisk fysik 15hp
MVE025 Komplex matematisk analys 6hp
FUF040 Kvantfysik 6hp
TMA660 Linjär algebra och geometri 4,5hp
TMA671 Linjär algebra och numerisk analys 7,5hp
TMA976 Matematisk analys, fortsättning 6hp
TMA321 Matematisk statistik 4,5hp
FFM515 Mekanik 1 7,5hp
FFM520 Mekanik 2 6hp
TIF075 Miljöfysik 4,5hp
FFY091 Optik 6hp
TIN211 Programmeringsteknik 6hp
ERE091 Reglerteknik 4,5hp
FUF045 Speciell relativitetsteori 4,5hp
TME055 Strömningsmekanik 4,5hp
FUF050 Subatomär fysik 6hp
FTF140 Termodynamik och statistisk fysik 7,5hp
TIF100 Tillämpad kvantfysik 4,5hp
FFM232 Vektorfält och klassisk fysik 4,5hp
{: .prettyprint .lang-betyg}
TDA305 Datorintroduktion 1,5hp
ESS115 Elektriska nät och system 7,5hp
EEF031 Elektromagnetisk fältteori 7,5hp
TIF080 Experimentell fysik 1 - mätteknik 9hp
TIF090 Experimentell fysik 2 - bas 4,5hp
FFY011 Fasta tillståndets fysik 7,5hp
MVE035 Flervariabelanalys 6hp
MVE030 Fourieranalys 6hp
TIF010 Fysiken omkring oss 7,5hp
TMA970 Inledande matematisk analys 6hp
FKA150 Inledande teknisk kommunikation 1,5hp
TIFX02 Kandidatarbete vid Teknisk fysik 15hp
MVE025 Komplex matematisk analys 6hp
FUF040 Kvantfysik 6hp
TMA660 Linjär algebra och geometri 4,5hp
TMA671 Linjär algebra och numerisk analys 7,5hp
TMA976 Matematisk analys, fortsättning 6hp
TMA321 Matematisk statistik 4,5hp
FFM515 Mekanik 1 7,5hp
FFM520 Mekanik 2 6hp
TIF075 Miljöfysik 4,5hp
FFY091 Optik 6hp
TIN211 Programmeringsteknik 6hp
ERE091 Reglerteknik 4,5hp
FUF045 Speciell relativitetsteori 4,5hp
TME055 Strömningsmekanik 4,5hp
FUF050 Subatomär fysik 6hp
FTF140 Termodynamik och statistisk fysik 7,5hp
TIF100 Tillämpad kvantfysik 4,5hp
FFM232 Vektorfält och klassisk fysik 4,5hp
###Bachelors thesis
For our bachelors thesis me, two of my fellow students at Engineering
@ -57,20 +56,19 @@ Grades can be provided on request.
As of January 2013 I have just begun work on my Masters thesis.
TIN092 Algorithms 7,5hp
FFR135 Artificial neural networks 7,5hp
FFR125 Autonomous agents 7,5hp
FFR141 Complex systems seminar 7,5hp
FFR110 Computational biology 1 7,5hp
TDA351 Cryptography 7,5hp
TIF115 Dynamical systems 7,5hp
TIF160 Humanoid robotics 7,5hp
RRY025 Image processing 7,5hp
MVE370 Matematik och samhälle 7,5hp
FFR120 Simulation of complex systems 7,5hp
FFR105 Stochastic optimization algorithms 7,5hp
MVE080 Vetenskaplig visualisering 7,5hp
{: .prettyprint .lang-betyg}
TIN092 Algorithms 7,5hp
FFR135 Artificial neural networks 7,5hp
FFR125 Autonomous agents 7,5hp
FFR141 Complex systems seminar 7,5hp
FFR110 Computational biology 1 7,5hp
TDA351 Cryptography 7,5hp
TIF115 Dynamical systems 7,5hp
TIF160 Humanoid robotics 7,5hp
RRY025 Image processing 7,5hp
MVE370 Matematik och samhälle 7,5hp
FFR120 Simulation of complex systems 7,5hp
FFR105 Stochastic optimization algorithms 7,5hp
MVE080 Vetenskaplig visualisering 7,5hp
__Matematik och samhälle__ translates to __Mathematics and society__.

View File

@ -13,6 +13,7 @@ DEBUG = True
FLATPAGES_AUTO_RELOAD = DEBUG
FLATPAGES_EXTENSION = '.md'
FLATPAGES_ROOT = 'pages'
FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite', 'extra']
app = Flask(__name__)
app.config.from_object(__name__)

View File

@ -20,7 +20,7 @@
<!-- MathJax.Hub.Config({ -->
<!-- tex2jax: { -->
<!-- skipTags: ["script","noscript","style","textarea","code"], -->
<!-- ignoreClass: ".*", -->
<!-- ignoreClass: ".*", -->
<!-- processClass: "latex" -->
<!-- } -->
<!-- }); -->
@ -67,12 +67,12 @@
</div><!--wrap-->
<div id="bottom">
&copy; 2012
<a href="mailto:thomasloven@gmail.com">Thomas Lovén</a>
-
&copy; 2012
<a href="mailto:thomasloven@gmail.com">Thomas Lovén</a>
-
<a href="http://twitter.com/thomasloven">@thomasloven</a>
-
<a href="http://github.com/thomasloven">GitHub</a>
-
<a href="http://github.com/thomasloven">GitHub</a>
</div><!--bottom-->

View File

@ -2,14 +2,20 @@
{% block page %}
<div id="categories">
<a href="{{url_for("blog_default")}}">All</a>
{% if title %}
<a href="{{url_for("blog_default")}}">All</a>
{% else %}
All
{% endif %}
{% for tag in tags %}
-
<a href="{{url_for("tag", tag=tag)}}">{{ tag }}</a>
{% if tag == title %}
-
{{ tag }}
{% else %}
-
<a href="{{url_for("tag", tag=tag)}}">{{ tag }}</a>
{% endif %}
{% endfor %}
{% if title %}
{{ title }}
{% endif %}
</div>
<div class="content">
<ol class="content-list">

View File

@ -7,12 +7,12 @@
</div>
<div id="post-text">
{{ page }}
<h2 id="comment">Comments</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
<h2 id="comment">Comments</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'thomasloven'; // required: replace example with your forum shortname
var disqus_identifier = '{{ page.url[:-1] }}';
var disqus_identifier = '{{ page.url[:-1] }}';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
@ -23,6 +23,6 @@
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div>
{% endblock page %}