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: The class looks as follows:
:::latex
%
% personal_letter
%
% Created by Thomas Loven on 2010-02-24. Feel free to use.
%
% \ProvidesClass{personal_letter}
% 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}
% Define functions for user changeable variables % Based on article
\makeatletter \LoadClass[a4paper, oneside]{article}
\newcommand{\adress}[1]{\def \@adress{#1}} \usepackage{ifthen}
\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 % Define functions for user changeable variables
\usepackage{ifpdf} \makeatletter
\ifpdf
\usepackage[pdftex]{graphicx} \newcommand{\adress}[1]{\def \@adress{#1}}
\DeclareGraphicsExtensions{.pdf, .jpg, .tif} \newcommand{\telephone}[1]{\def \@telephone{#1}}
\else \newcommand{\email}[1]{\def \@email{#1}}
\usepackage{graphicx} \newcommand{\name}[1]{\def \@name{#1}}
\DeclareGraphicsExtensions{.eps, .jpg} \newcommand{\place}[1]{\def \@place{#1}}
\fi %\newcommand{\date}[1]{\def \@date{#1}}
\newcommand{\greeting}[1]{\def \@greeting{#1}}
\newcommand{\closing}[1]{\def \@closing{#1}}
% Costruct heading \newcommand{\url}[1]{\def \@url{#1}}
\makeatletter
\fancyfoot[LO, LE]{\@adress} \adress{}
\fancyfoot[RO, RE]{ \ifthenelse{\equal{\@telephone}{}}{}{ \@telephone \\ \@email \\ \@url }} \telephone{}
\fancyfoot[C]{} \email{}
\setlength{\headheight}{0 cm} \name{}
\setlength{\headsep}{0 cm} \place{}
\renewcommand{\headrulewidth}{0pt} \date{}
\addtolength{\textheight}{0 cm} \greeting{}
\renewcommand{\footrulewidth}{0.5pt} \closing{}
\pagestyle{fancy} \url{}
\makeatletter
\makeatother
%Body of the letter
\makeatletter % Include usefull packages
\newenvironment{body}% { \usepackage[utf8]{inputenc}
\begin{quotation} \usepackage{fullpage}
\begin{onehalfspace} \usepackage{fancyhdr}
\setlength{\parskip}{1ex} \usepackage{setspace}
\usepackage[swedish]{babel}
\ifthenelse{\equal{\@place}{}}{}{ \begin{flushright} \@place \\ \@date \end{flushright} } \usepackage{longtable}
\vspace{2ex} % Make graphics work with pdf or dvi files
\bf \usepackage{ifpdf}
\noindent \ifpdf
\@greeting \usepackage[pdftex]{graphicx}
\rm \DeclareGraphicsExtensions{.pdf, .jpg, .tif}
}% \else
{ \usepackage{graphicx}
\vspace{10pt} \DeclareGraphicsExtensions{.eps, .jpg}
\ifthenelse{\equal{\@closing}{}}{}{ \noindent \@closing \\ \noindent \@name } \end{onehalfspace} \end{quotation} \fi
}
\makeatother
% Costruct heading
\newcommand{\titel}[1]{ \begin{centering} {\Huge #1} \end{centering} } \makeatletter
\fancyfoot[LO, LE]{\@adress}
\newcommand{\rubrik}[1]{ \vspace{1.5em} \fancyfoot[RO, RE]{ \ifthenelse{\equal{\@telephone}{}}{}{ \@telephone \\ \@email \\ \@url }}
\fancyfoot[C]{}
{\large \bf #1} \setlength{\headheight}{0 cm}
\setlength{\headsep}{0 cm}
} \renewcommand{\headrulewidth}{0pt}
{: .prettyprint .lang-tex} \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. Plain and simple.
And a usage example: And a usage example:
\documentclass{personal_letter} :::latex
\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.
Donec gravida metus eu dui rutrum nec bibendum libero molestie. \place{Göteborg}
Aenean et odio massa. Donec pulvinar augue non tellus vulputate nec \adress{Thomas Lovén \\ Xxxxxxxxxxxxxxx XX \\ XXX XX Xxxxxx Xxxxxxxx}
congue justo accumsan. Nam pretium sagittis dictum. Sed semper auctor \telephone{+XX XX XXX XX XX}
neque in commodo. Mauris dignissim ante ac nibh pretium consequat. \email{thomasloven@gmail.com}
Donec orci tortor, pharetra non congue vel, ultrices sit amet lacus. \url{thomasloven.wordpress.com}
Suspendisse a lacus nec ante venenatis bibendum vitae id dui. Nam
semper arcu facilisis nunc euismod volutpat. Donec accumsan velit nec \begin{document}
ante lacinia pulvinar. Phasellus ut varius enim. Pellentesque vel
augue odio. Suspendisse sed nisi vel magna euismod semper. Maecenas \greeting{Salvete!}
erat neque, tristique id consequat id, mollis eu enim. Phasellus \closing{Di vos incolumes custodiant.}
laoreet pulvinar ante accumsan posuere. Proin viverra dui id ipsum
hendrerit non mollis mi rutrum. \begin{body}
\end{body} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
non arcu non massa accumsan tincidunt. Suspendisse non est quis
\end{document} massa sollicitudin faucibus. Quisque gravida vulputate nisi pharetra
{: .prettyprint .lang-tex} 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) ![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! Let's go!
curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz $ 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 $ tar -zxvf bochs-2.5.1.tar.gz
mkdir build-bochs $ mkdir build-bochs
cd 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
../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 all $ make install
make install
{: .prettyprint .lan-sh}
And that's the terminal version. ### Important note This configure line won't And that's the terminal version.
actually work. I'll update with the new one as soon as I can.
### 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. Now we move this to *bochs-term* and compile the version with the debugger.
mv /usr/local/bin/bochs /usr/local/bin/bochs-term $ mv /usr/local/bin/bochs /usr/local/bin/bochs-term
$ rm *
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
../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 all $ make install
make install
{: .prettyprint .lan-sh}
Finally, copy the bochs bioses to a public place Finally, copy the bochs bioses to a public place
cp -r ../bochs-2.5.1/bios /usr/share/bochs $ cp -r ../bochs-2.5.1/bios /usr/share/bochs
{: .prettyprint .lan-sh}
And that's it. 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 Compiling gcc also requires the mpfr package to be installed. This I did with
[Homebrew](http://mxcl.github.com/homebrew/). [Homebrew](http://mxcl.github.com/homebrew/).
brew install mpfr :::bash
{: .prettyprint} $ brew install mpfr
I downloaded all the sources I needed from [gnu.org](http://gnu.org). 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 :::bash
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/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/gmp/gmp-5.0.2.tar.gz
curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.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 $ curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
{: .prettyprint .lang-bash}
Feel free to use later versions, but if you do, I cannot guarantee that the 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 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 gcc, so after extracting all archives, they are simply copied into the gcc
source source
mv gmp-5.0.2 gcc-4.6.3/gmp :::bash
mv mpfr-3.1.0 gcc-4.6.3/mpfr $ mv gmp-5.0.2 gcc-4.6.3/gmp
mv mpc-0.9 gcc-4.6.3/mpc $ mv mpfr-3.1.0 gcc-4.6.3/mpfr
{: .prettyprint .lang-sh} $ 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. In order not to mess up the source, binutils and gcc were built out of tree.
mkdir build-binutils :::bash
cd build-binutils $ mkdir build-binutils
$ cd build-binutils
export PREFIX=/usr/local/cross $ export PREFIX=/usr/local/cross
export TARGET=i386-elf $ export TARGET=i386-elf
../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls $ ../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
make all $ make all
make install $ make install
{: .prettyprint .lang-bash}
And the same for gcc, using the new binutils And the same for gcc, using the new binutils
cd .. :::bash
mkdir build-gcc $ cd ..
cd build-gcc $ mkdir build-gcc
export PATH=$PATH:$PREFIX/bin $ cd build-gcc
../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers $ export PATH=$PATH:$PREFIX/bin
make all-gcc $ ../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
make install-gcc $ make all-gcc
{: .prettyprint .lang-bash} $ make install-gcc
It's really important to run _make all-gcc_ and _make install-gcc_ and __not__ 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 _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 repository](http://github.com/thomasloven/os5). The directory structure starts
off as follows off as follows
os5/ os5/
|-- build/ |-- build/
|-- include/ |-- include/
`-- kernel/ `-- kernel/
`-- include/ `-- include/
A difference from previous times is that the _include_ directory is outside the 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 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 move it into some library directory later. Know what? Let's move it into
a library directory right now... a library directory right now...
os5/ os5/
|-- build/ |-- build/
|-- kernel/ |-- kernel/
| `-- include/ | `-- include/
`-- library/ `-- library/
`-- include/ `-- include/
The build/ directory contains some scripts needed for building and testing the The build/ directory contains some scripts needed for building and testing the
os as well as a floppy image preinstalled with the GRUB bootloader. 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 main makefile is to run the makefile in kernel/, copy the kernel image into
the floppy and then run bochs-term. the floppy and then run bochs-term.
BUILDDIR := $(PWD)
PATH := /usr/local/cross/bin:$(PATH) :::make
DIRS := kernel BUILDDIR := $(PWD)
TARGET := i386-elf
AS := nasm PATH := /usr/local/cross/bin:$(PATH)
CC := i386-elf-gcc DIRS := kernel
LD := i386-elf-ld TARGET := i386-elf
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 AS := nasm
@echo " STARTING EMULATOR" CC := i386-elf-gcc
@build/emul.sh LD := i386-elf-ld
force: ASFLAGS := -f elf
true CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
{: .prettyprint} 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. The makefile in the kernel/ directory is pretty much straight forward.
TARGET := kernel :::make
SUBDIR := kernel TARGET := kernel
SUBDIR := kernel
SOURCES := kinit.o boot.o
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s)) SOURCES := kinit.o boot.o
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c)) 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 CCFLAGS += -Iinclude
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
.SUFFICES: .o .s .c
.SUFFICES: .o .s .c
all: $(TARGET)
all: $(TARGET)
$(TARGET): $(SOURCES)
@echo " ln " $(TARGET) $(TARGET): $(SOURCES)
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES) @echo " ln " $(TARGET)
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
.c.o:
@echo " gcc " $< .c.o:
@$(CC) $(CFLAGS) -c $< -o $@ @echo " gcc " $<
@$(CC) $(CFLAGS) -c $< -o $@
.s.o:
@echo " nasm " $< .s.o:
@$(AS) $(ASFLAGS) $< -o $@ @echo " nasm " $<
@$(AS) $(ASFLAGS) $< -o $@
clean:
-@rm $(SOURCES) 2>/dev/null clean:
-@rm $(TARGET) 2>/dev/null -@rm $(SOURCES) 2>/dev/null
{: .prettyprint} -@rm $(TARGET) 2>/dev/null

View File

@ -19,34 +19,33 @@ a special Linker file for the kernel.
*kernel/include/Link.ld* *kernel/include/Link.ld*
ENTRY(start) ENTRY(start)
SECTIONS { SECTIONS {
. = 0xC0100000; . = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000) .text : AT(ADDR(.text) - 0xC0000000)
{ {
code = .; _code = .; __code = .; code = .; _code = .; __code = .;
*(.text) *(.text)
*(.eh_frame) *(.eh_frame)
. = ALIGN(4096); . = ALIGN(4096);
} }
.data : AT(ADDR(.data) - 0xC0000000) .data : AT(ADDR(.data) - 0xC0000000)
{ {
data = .; _data = .; __data = .; data = .; _data = .; __data = .;
*(.data) *(.data)
*(.rodata) *(.rodata)
. = ALIGN(4096); . = ALIGN(4096);
} }
.bss : AT(ADDR(.bss) - 0xC0000000) .bss : AT(ADDR(.bss) - 0xC0000000)
{ {
bss = .; _bss = .; __bss = .; bss = .; _bss = .; __bss = .;
*(.bss) *(.bss)
. = ALIGN(4096); . = ALIGN(4096);
} }
_end = .; _end = .;
} }
{: .prettyprint .linenums}
GRUB drops us off at the kernel entry point - *start* as defined in the 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 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/boot.s*
; Kernel start point :::nasm
[global start] ; Kernel start point
start: [global start]
cli start:
cli
; Load page directory and enable paging
mov ecx, BootPageDirectory - KERNEL_OFFSET ; Load page directory and enable paging
mov cr3, ecx mov ecx, BootPageDirectory - KERNEL_OFFSET
mov ecx, cr0 mov cr3, ecx
or ecx, 0x80000000 mov ecx, cr0
mov cr0, ecx or ecx, 0x80000000
lea ecx, [.higherHalf] mov cr0, ecx
jmp ecx lea ecx, [.higherHalf]
jmp ecx
.higherHalf:
; Load GDT .higherHalf:
mov ecx, gdt_ptr ; Load GDT
lgdt [ecx] mov ecx, gdt_ptr
lgdt [ecx]
SetSegments 0x10, cx
jmp 0x8:.gdtLoaded SetSegments 0x10, cx
jmp 0x8:.gdtLoaded
.gdtLoaded:
{: .prettyprint .lang-nasm .linenums:61} .gdtLoaded:
Here's another new thing for me. Macros. Can't believe I could do without them 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 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* *kernel/include/asm_macros.inc*
; GRUB multiboot headers :::nasm
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0 ; GRUB multiboot headers
MBOOT_MEMORY_INFO_FLAG equ 1<<1 MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
MBOOT_HEADER_MAGIC equ 0x1BADB002 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) 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 KERNEL_OFFSET equ 0xC0000000
BOOT_STACK_SIZE equ 0x1FFF
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
%macro SetSegments 2 ; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
mov e%2, %1 %macro SetSegments 2
mov ds, %2 mov e%2, %1
mov es, %2 mov ds, %2
mov fs, %2 mov es, %2
mov gs, %2 mov fs, %2
mov ss, %2 mov gs, %2
%endmacro mov ss, %2
{: .prettyprint .lang-nasm .linenums:2} %endmacro
There are also references to some data structures, i.e. *BootPageDirectory* and There are also references to some data structures, i.e. *BootPageDirectory* and
*gdt_ptr*. Those are hardcoded in the bootstrap file. *gdt_ptr*. Those are hardcoded in the bootstrap file.
*kernel/boot.s* *kernel/boot.s*
:::nasm
%include "include/asm_macros.inc" %include "include/asm_macros.inc"
[bits 32] [bits 32]
section .bss section .bss
align 0x8 align 0x8
; Stack for booting ; Stack for booting
[global BootStack] [global BootStack]
BootStackTop: BootStackTop:
resb BOOT_STACK_SIZE resb BOOT_STACK_SIZE
BootStack: BootStack:
section .data section .data
align 0x1000 align 0x1000
; Page directory for booting up. ; Page directory for booting up.
; First four megabytes are identity mapped as well as ; First four megabytes are identity mapped as well as
; mapped to 0xC0000000 ; mapped to 0xC0000000
[global BootPageDirectory] [global BootPageDirectory]
BootPageDirectory: BootPageDirectory:
dd (BootPageTable - KERNEL_OFFSET) + 0x3 dd (BootPageTable - KERNEL_OFFSET) + 0x3
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0 times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
dd (BootPageTable - KERNEL_OFFSET) + 0x3 dd (BootPageTable - KERNEL_OFFSET) + 0x3
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0 times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3 dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
BootPageTable: BootPageTable:
%assign i 0 %assign i 0
%rep 1024 %rep 1024
dd (i << 12) | 0x3 dd (i << 12) | 0x3
%assign i i+1 %assign i i+1
%endrep %endrep
; Hard-coded GDT. ; Hard-coded GDT.
; GDT pointer is wrapped into the first entry ; GDT pointer is wrapped into the first entry
[global gdt] [global gdt]
gdt_ptr: gdt_ptr:
gdt: gdt:
dw 0x002F dw 0x002F
dd gdt dd gdt
dw 0x0000 dw 0x0000
dd 0x0000FFFF, 0x00CF9A00 dd 0x0000FFFF, 0x00CF9A00
dd 0x0000FFFF, 0x00CF9200 dd 0x0000FFFF, 0x00CF9200
dd 0x0000FFFF, 0x00CFFA00 dd 0x0000FFFF, 0x00CFFA00
dd 0x0000FFFF, 0x00CFF200 dd 0x0000FFFF, 0x00CFF200
section .text section .text
align 4 align 4
; GRUB Multiboot data ; GRUB Multiboot data
MultiBootHeader: MultiBootHeader:
dd MBOOT_HEADER_MAGIC dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS dd MBOOT_HEADER_FLAGS
dd MBOOT_HEADER_CHECKSUM dd MBOOT_HEADER_CHECKSUM
{: .prettyprint .lang-nasm .linenums:2}
Well. This is first of all some area saved for a stack. Then there's the page 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 directory which has the same page table at virtual memory 0x00000000 and
@ -191,29 +189,29 @@ from the page directory.
*kernel/boot.s* *kernel/boot.s*
.gdtLoaded: :::nasm
; Clear the identity mapping from the page directory .gdtLoaded:
mov edx, BootPageDirectory ; Clear the identity mapping from the page directory
xor ecx, ecx mov edx, BootPageDirectory
mov [edx], ecx xor ecx, ecx
invlpg[0] mov [edx], ecx
invlpg[0]
; Load a stack for booting
mov esp, BootStack ; Load a stack for booting
mov ebp, BootStack mov esp, BootStack
mov ebp, BootStack
; eax contains the magic number from GRUB 0x2BADB002
push eax ; eax contains the magic number from GRUB 0x2BADB002
push eax
; ebx contains the address of the Multiboot information structure
add ebx, KERNEL_OFFSET ; ebx contains the address of the Multiboot information structure
push ebx add ebx, KERNEL_OFFSET
push ebx
; Call the c function for setting up
[extern kinit] ; Call the c function for setting up
call kinit [extern kinit]
jmp $ call kinit
{: .prettyprint .lang-nasm .linenums:83} jmp $
The final thing we do before jumping into the c kernel stub is push the values 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 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* *kernel/kinit.c*
:::c
void kinit() void kinit()
{ {
} }
{: .prettyprint .linenums:2}
Compiling and running this through bochs, we are presented with a black and Compiling and running this through bochs, we are presented with a black and
white screen completely void of error messages. Perfect! 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 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 between assembly and c code. In assembler, you define a constant value as
EXACT_PI equ 3 :::nasm
{: .prettyprint .lang-nasm} EXACT_PI equ 3
and in c and in c
#define EXACT_PI 3 :::c
{: .prettyprint .lang-c} #define EXACT_PI 3
As is usually the case with things that annoy me, there is of course a solution 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. 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_ _myAsmFile.asm_
#include <header.h> :::nasm
#include <header.h>
mov eax, EXACT_PI
{: .prettyprint .lang-nasm} mov eax, EXACT_PI
_include/header.h_ _include/header.h_
#pragma once :::c
#pragma once
#define EXACT_PI 3
#define EXACT_PI 3
#ifndef __ASSEMBLER__
// This is not evaluated if header.h is included from an assembly file. #ifndef __ASSEMBLER__
#endif // This is not evaluated if header.h is included from an assembly file.
{: .prettyprint .lang-c} #endif
This is compiled through: This is compiled through:
cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s :::bash
nasm myAsmFile.s $ cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
{: .prettyprint} $ nasm myAsmFile.s
The _-x_-flag tells the preprocessor what type of file the following input 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 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 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 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. the next five pages, who are free. The rightmost page is handed out.
![PMM1](/media/img/pmm1b.png){: .noborder .center} ![PMM1](/media/img/pmm1b.png){: .noborder .center}
When the _pmm_ receives a request for a memory page it will pop the topmost 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 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 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 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 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, 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 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, 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 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 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 In other words, you can access any page table through a fixed address in
memory. But wait, it gets even better. 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 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 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 - 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 ###Some considerations
An important question to put at this point is whether a recursive page 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 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 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 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 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 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 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 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 Finally, if a recursive page directory is used on an x86, the following can be
used to access the page directories and tables: used to access the page directories and tables:
uint32_t *page_dir = 0xFFFFF000; :::c
uint32_t *page_tables = 0xFFC00000; uint32_t *page_dir = 0xFFFFF000;
uint32_t *page_tables = 0xFFC00000;
//addr = virtual address
//phys = physical address (page alligned) //addr = virtual address
//flags = access flags //phys = physical address (page alligned)
//flags = access flags
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
page_tables[addr >> 12] = phys | flags; page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
{:.prettyprint} page_tables[addr >> 12] = phys | flags;
###Git ###Git
A recursive page directory has been implemented in Git commit 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 The simplest possible memory manager just hands out a new address each time
_malloc_ is called and doesn't care when memory is freed. _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); void *malloc(uint32_t size);
{ {
memory_pointer = memory_pointer + size; memory_pointer = memory_pointer + size;
return memory_pointer - size; return memory_pointer - size;
} }
void free(void *mem) void free(void *mem)
{ {
; ;
} }
{: .prettyprint}
###Heap ###Heap
The next method - which I prefer - is a memory 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 The heap consists of a list of free memory areas. In the simplest possible
variety, it would look something like this: variety, it would look something like this:
struct area_header :::c
{ struct area_header
uint32_t size; {
struct free_area *next; uint32_t size;
}; struct free_area *next;
};
void *malloc(uint32_t size)
{ void *malloc(uint32_t size)
struct area_header *area = heap_list_head; {
while(area) struct area_header *area = heap_list_head;
{ while(area)
if(area->size >= size) {
{ if(area->size >= size)
remove_from_heap_list(area); {
return get_memory_area(area); remove_from_heap_list(area);
} return get_memory_area(area);
area = area->next; }
} area = area->next;
panic("Out of memory!"); }
} panic("Out of memory!");
}
void free(void *mem)
{ void free(void *mem)
struct area_header area = get_area_header(mem); {
insert_into_heap_list(area); struct area_header area = get_area_header(mem);
} insert_into_heap_list(area);
{: .prettyprint} }
Here it is assumed that the free memory is already divided into smaller areas 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 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. Today I was writing some code for handling interrupts.
At one point I needed the following piece of code 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); :::c
{: .prettyprint} 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 ###The solution
Vim macros. 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 learn vim. And today it payed off. To write the above piece of code I used the
key presses 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! Couldn't be easier!
@ -25,7 +25,7 @@ OK, so maybe it could... Let's break it down.
Let's start with Let's start with
iisr0(void),<esc> iisr0(void),<esc>
_i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave _i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave
Insert mode with the escape key. 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 Next is _0_ to bring the pointer to the beginning of the line. Then comes the
macro. macro.
qayyp3l<ctrl>a0q qayyp3l<ctrl>a0q
_qa_ starts recording a macro into register a. _qa_ starts recording a macro into register a.
@ -48,13 +48,13 @@ recording.
The next part: The next part:
46@a47k48J 46@a47k48J
runs the macro 46 times, steps up 47 times and joins the current line with the runs the macro 46 times, steps up 47 times and joins the current line with the
next 48 times. We now have 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), :::c
{: .prettyprint} 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 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_. _$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 Starting with
INTNOERR 0 :::nasm
{: .prettyprint} INTNOERR 0
I used I used
qayypcwINTNOERR<esc>$<ctrl>a0q qayypcwINTNOERR<esc>$<ctrl>a0q
qsyypcwINTERR<esc>$<ctrl>a0q qsyypcwINTERR<esc>$<ctrl>a0q
dd6@a@s@a5@s33@a dd6@a@s@a5@s33@a
and ended up with and ended up with
INTNOERR 0 :::nasm
INTNOERR 1 INTNOERR 0
INTNOERR 2 INTNOERR 1
INTNOERR 3 INTNOERR 2
INTNOERR 4 INTNOERR 3
INTNOERR 5 INTNOERR 4
INTNOERR 6 INTNOERR 5
INTNOERR 7 INTNOERR 6
INTERR 8 INTNOERR 7
INTNOERR 9 INTERR 8
INTERR 10 INTNOERR 9
INTERR 11 INTERR 10
INTERR 12 INTERR 11
INTERR 13 INTERR 12
INTERR 14 INTERR 13
INTNOERR 15 INTERR 14
... INTNOERR 15
INTNOERR 45 ...
INTNOERR 46 INTNOERR 45
{: .prettyprint} INTNOERR 46
I love vim! I love vim!
###Application ###Application
So where did I use this? I've been writing some code for handling interrupts in 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 the os. You can find it in Git commit
[26dd8e4c75](https://github.com/thomasloven/os5/tree/26dd8e4c7507b66e4f94bf2c4e980265c6f0a20b). [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 the property list of _Springboard_-the main interface of iOS. This is located
at 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 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 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 work as a kind of translation table. In Protected mode, if you call an address
like like
jmp CS:AX :::nasm
{: .prettyprint .lang-nasm} jmp CS:AX
the processor looks into the currently loaded __Local__ or __Global Descriptor the processor looks into the currently loaded __Local__ or __Global Descriptor
Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or
__Segment Descriptor__) describes the beginning of a segment which is combined __Segment Descriptor__) describes the beginning of a segment which is combined
with the offset in _AX_ to get the physical address; with the offset in _AX_ to get the physical address;
physical_address = segment_descriptor_from_index(CS).base + AX; :::c
{: .prettyprint} physical_address = segment_descriptor_from_index(CS).base + AX;
The segment descriptor also has a limit, which in our example is the maximum 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__ 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 Increasing the CPL is relatively easy. It can be done either through a far jump
JMP 0x1B:label :::nasm
label: JMP 0x1B:label
; The CS selector is now 0x18 | 0x3 label:
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3 ; The CS selector is now 0x18 | 0x3
{: .prettyprint .lang-nasm} ; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
or through the `IRET` instruction 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 An other (better in my opinion) option is to create a fake interrupt-pushed
stack and push that onto the stack before running `IRET` . stack and push that onto the stack before running `IRET` .
// C code :::c
struct // C code
{ struct
uint32_t esp; {
uint32_t ss; uint32_t esp;
uint32_t eflags; uint32_t ss;
uint32_t eip; uint32_t eflags;
uint32_t cs; uint32_t eip;
} fake_stack; uint32_t cs;
} fake_stack;
fake_stack.esp = usermode_stack_top; fake_stack.esp = usermode_stack_top;
fake_stack.ss = user_data_segment | 0x3; fake_stack.ss = user_data_segment | 0x3;
fake_stack.eflags = 0; fake_stack.eflags = 0;
fake_stack.eip = &usermode_function; fake_stack.eip = &usermode_function;
fake_stack.cs = user_code_segment | 0x3; fake_stack.cs = user_code_segment | 0x3;
set_all_segments(user_data_segment | 0x3);
run_iret(&fake_stack);
{: .prettyprint}
; Assembler code set_all_segments(user_data_segment | 0x3);
run_iret: run_iret(&fake_stack);
add esp, 0x8
iret
{: .prettyprint .lang-nasm}
&nbsp;
:::nasm
; Assembler code
run_iret:
add esp, 0x8
iret
###Going back to ring0 ###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 (source: [Intel
Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/)) Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/))
gdt[TSS_DESCRIPTOR].base = &tss; :::c
gdt[TSS_DESCRIPTOR].limit = sizeof(tss); gdt[TSS_DESCRIPTOR].base = &tss;
gdt[TSS_DESCRIPTOR].flags = 0; gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED; gdt[TSS_DESCRIPTOR].flags = 0;
{: .prettyprint} 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 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 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. stacks inside a function, when you return, you'll be somewhere else.
This is a common way of making usermode threads. Ponder the following: This is a common way of making usermode threads. Ponder the following:
void switch_thread() :::c
{ void switch_thread()
push_all_registers(); {
switch_stack_pointer(); push_all_registers();
pop_all_registers(); switch_stack_pointer();
return; pop_all_registers();
} return;
}
void a()
{
while(1)
{
do_something();
switch_thread();
}
}
void b() void a()
{ {
while(1) while(1)
{ {
do_something_else(); do_something();
switch_thread(); switch_thread();
} }
} }
void b()
{
while(1)
{
do_something_else();
switch_thread();
}
}
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__ 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 runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
running. The top of the stacks looks like: running. The top of the stacks looks like:
+-----------------------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
+----------ESP----------+ |switch_thread RA | +----------ESP----------+ |switch_thread RA |
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
where `RA` means Return Address and `ESP` is where the stack pointer is where `RA` means Return Address and `ESP` is where the stack pointer is
currently pointing. currently pointing.
As execution of __A__ continues, the processor will `do_something()` and As execution of __A__ continues, the processor will `do_something()` and
then call `switch_thread()`... then call `switch_thread()`...
+-----------------------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
+----------ESP----------+ |all registers | +----------ESP----------+ |all registers |
|switch_thread RA | |switch_thread RA | |switch_thread RA | |switch_thread RA |
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
`switch_thread()` pushes all registers to the stack and calls `switch_thread()` pushes all registers to the stack and calls
`switch_stack_pointer()` `switch_stack_pointer()`
+----------ESP----------+ +-----------------------+ +----------ESP----------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA| |switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers | |all registers | |all registers |
|switch_thread RA | |switch_thread RA | |switch_thread RA | |switch_thread RA |
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
`switch_stack_pointer()` performs some scheduling to find out which `switch_stack_pointer()` performs some scheduling to find out which
thread is to run next, and then switches the stack pointer over to the thread is to run next, and then switches the stack pointer over to the
top of __B__'s stack. top of __B__'s stack.
+-----------------------+ +----------ESP----------+ +-----------------------+ +----------ESP----------+
|switch_stack_pointer RA| |switch_stack_pointer RA| |switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers | |all registers | |all registers |
|switch_thread RA | |switch_thread RA | |switch_thread RA | |switch_thread RA |
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
The processor keeps on executing code, and `switch_stack_pointer()` soon The processor keeps on executing code, and `switch_stack_pointer()` soon
returns returns
+-----------------------+ +-----------------------+
|switch_stack_pointer RA| +----------ESP----------+ |switch_stack_pointer RA| +----------ESP----------+
|all registers | |all registers | |all registers | |all registers |
|switch_thread RA | |switch_thread RA | |switch_thread RA | |switch_thread RA |
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
`switch_thread()` pops all registers and returns... `switch_thread()` pops all registers and returns...
+-----------------------+ +-----------------------+
|switch_stack_pointer RA| |switch_stack_pointer RA|
|all registers | |all registers |
|switch_thread RA | +----------ESP----------+ |switch_thread RA | +----------ESP----------+
|a RA | |b RA | |a RA | |b RA |
| ... | | ... | | ... | | ... |
{: .nopretty}
... and we're now in `b()` with all registers of __B__ loaded. ... and we're now in `b()` with all registers of __B__ loaded.
###Stacks in the kernel ###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 Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
have something like this to handle interrupts: have something like this to handle interrupts:
int_stub: :::nasm
pusha 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}
void int_handler(registers_t r) xor eax, eax
{ mov ax, ds
do_stuff(); 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 In fact, if you've been following one of those tutorials, you probably
have the above code twice, for some reason... 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 and with only a small modification, it becomes very easy to switch the
stacks too... stacks too...
int_stub: :::nasm
pusha 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 }
registers_t *int_handler(registers_t *r) xor eax, eax
{ mov ax, ds
do_stuff(); push eax
r = get_next_thread(r);
return r; 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 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 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 While the thread is running we want some information stored somewhere in
kernel space about it. kernel space about it.
+-----------------------+ +-----------------------+
|thread information | |thread information |
+-----------------------+ +-----------------------+
{: .nopretty}
Then, when an interrupt or syscall happens, a new stack is loaded 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 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 information it will have to go right before it, since the stack grows
backwards. backwards.
+-----------------------+ +-----------------------+
|thread registers | |thread registers |
|thread information | |thread information |
+-----------------------+ +-----------------------+
{: .nopretty}
Finally, we want the kernel mode stack. Well... the stack pointer is 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 right at the start of the registers now, so why not just continue the
stack from there? stack from there?
+-----------------------+ +-----------------------+
| ... | | ... |
|kernel mode stack | |kernel mode stack |
|thread registers | |thread registers |
|thread information | |thread information |
+-----------------------+ +-----------------------+
{: .nopretty}
###Setting this up ###Setting this up
To set this up, the thread information structure has to be set up To set this up, the thread information structure has to be set up
something like: something like:
struct thread_info_struct :::c
{ struct thread_info_struct
uint8_t stack_space[KERNEL_STACK_SIZE]; {
registers_t r; uint8_t stack_space[KERNEL_STACK_SIZE];
struct thread_data_struct thread_data; registers_t r;
} my_thread_info; struct thread_data_struct thread_data;
} my_thread_info;
When the thread is running in user mode, the TSS should be set up in 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 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. 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 And that's really all there is to it. Unbelievable, really, how many
years it took for me to figure this out. years it took for me to figure this out.
In the process, I've found inspiration in [Rhombus by Nick 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). [linux](http://www.linux.org).
###Some considerations ###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 compiler](http://clang.llvm.org) does not use this calling convention
for functions which do not in turn call other functions. I.e for functions which do not in turn call other functions. I.e
int double_integer(int a) :::c
{ int double_integer(int a)
return 2*a; {
} return 2*a;
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
double_integer(5); double_integer(5);
} }
If this code is compiled with clang `double_integer` will (in some If this code is compiled with clang `double_integer` will (in some
cases) not push `ebp` to stack. 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: 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' :::bash
git checkout -b new_master OLD_COMMIT_SHA $ git commit -m 'Bad excuse for not checking in before'
git merge --strategy=ours master $ git checkout -b new_master OLD_COMMIT_SHA
git checkout master $ git merge --strategy=ours master
git merge new_master $ git checkout master
$ git merge new_master
The resulting commit is found at The resulting commit is found at
[f74ec287db](https://github.com/thomasloven/os5/tree/f74ec287db488a7bda5 [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 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 :::bash
{: .prettyprint .lang-sh} $ 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 `-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 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 current version of Xcode. Version 3.2 is installed by
[Homebrew](http://mxcl.github.com/homebrew/). [Homebrew](http://mxcl.github.com/homebrew/).
> homebrew install llvm :::bash
{: .prettyprint .lang-sh} $ homebrew install llvm
Binutils Binutils
-------- --------
@ -82,12 +82,14 @@ Instead, again, I use Homebrew.
First of all, to get a working cross target binutils, the brew formula First of all, to get a working cross target binutils, the brew formula
will have to be changed a bit 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`) 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 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. and you're good to go.
@ -122,48 +124,53 @@ a text mode (curses mode).
So I went out on a whim and tried 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 By now you should know pretty much what I think of Homebrew, so the
results of that command pretty much sealed the deal. results of that command pretty much sealed the deal.
Now I run my kernel in qemu through 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 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 functions I used most often in the bochs debugger, such as printing the
memory map. Further, this could be accessed using telnet from a memory map. Further, this could be accessed using telnet from a
different tmux pane. different tmux pane.
#!/bin/bash :::bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server' #!/bin/bash
tmux select-pane -L tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
telnet localhost 4444 tmux select-pane -L
{: .prettyprint} 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 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 :::bash
> brew edit gdb $ brew tap homebrew/dupes
$ brew edit gdb
Add the flag `--target=i386-elf` to the configure flags, save and 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. This will link to `ì386-elf-gdb` and can be run in yet another tmux window.
#!/bin/bash :::bash
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S' #!/bin/bash
tmux select-pane -L tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
tmux slit-window -v 'i386-elf-gdb' tmux select-pane -L
tmux select-pane -U tmux slit-window -v 'i386-elf-gdb'
telnet localhost 4444 tmux select-pane -U
telnet localhost 4444
Upon start, gdb will look for a file called `.gdbinit` which in my case contains Upon start, gdb will look for a file called `.gdbinit` which in my case contains
file kernel/kernel file kernel/kernel
target remote localhost:1234 target remote localhost:1234
Results Results
------- -------

View File

@ -1,6 +1,7 @@
layout: post layout: post
title: "Two Small Projects" title: "Two Small Projects"
subtitle: "Audio and Arduino" subtitle: "Audio and Arduino"
tags: [electronics]
###Breadboard Arduino ###Breadboard Arduino
I didn't really like the arduino when I first heard about it. It 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: For example:
#include <stdio.h> :::c
#include <unistd.h> #include <stdio.h>
#include <sys/wait.h> #include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{ int main(int argc, char **argv)
int variable = 1; {
int pid = fork(); int variable = 1;
int status; int pid = fork();
int status;
if( pid )
{ if( pid )
// This is the parent {
printf("Parent says: %d\n", variable); // This is the parent
variable = 2; printf("Parent says: %d\n", variable);
printf("Parent says: %d\n", variable); variable = 2;
waitpid(pid, &status, 0); // Let the child run printf("Parent says: %d\n", variable);
printf("Parent says: %d\n", variable); waitpid(pid, &status, 0); // Let the child run
} else { printf("Parent says: %d\n", variable);
// This is the child } else {
printf("Child says: %d\n", variable); // This is the child
variable = 3; printf("Child says: %d\n", variable);
printf("Child says: %d\n", variable); variable = 3;
} printf("Child says: %d\n", variable);
}
return 0;
} return 0;
{: .lang-c} }
This simple program should output (assuming the parent is run first and This simple program should output (assuming the parent is run first and
is not interrupted): is not interrupted):
Parent says: 1 Parent says: 1
Parent says: 2 Parent says: 2
Child says: 1 Child says: 1
Child says: 3 Child says: 3
Parent says: 2 Parent says: 2
The virtual memory of the X86 architecture allows us to switch out the 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. 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. Let's follow a memory area during part of a process' life.
###Setup ###Setup
![PROCMM1](/media/img/procmm1.png){: .center .noborder} ![PROCMM1](/media/img/procmm1.png){: .center .noborder}
In the figure above we see two processes, _A_ and _B_. 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 The user types
> gcc hello_world.c :::bash
$ gcc hello_world.c
into the terminal and the shell program executes the `fork` system call. 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 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 memory map for the new process. It then clones all memory areas into the
new map. new map.
![PROCMM2](/media/img/procmm2.png){: .center .noborder} ![PROCMM2](/media/img/procmm2.png){: .center .noborder}
The write flag of our area is unset and the CoW flag is set. The area is 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 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 the required area has no copies, so it is just set as read/write and
we're done. we're done.
![PROCMM4](/media/img/procmm4.png){: .center .noborder} ![PROCMM4](/media/img/procmm4.png){: .center .noborder}
Actually, the parent process will probably perform a `waitpid` syscall 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 System calls is the way user processes communicate to the kernel. Look
at the following program, for example. at the following program, for example.
#include <stdio.h> :::c
#include <stdio.h>
int main(int argc, char **argv)
{ int main(int argc, char **argv)
printf("Hello, world!"); {
printf("Hello, world!");
return 0;
} return 0;
{: .lang-c} }
When you call the program, even before it is started, the shell makes a 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 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 ####User side
First the definition in the c library: First the definition in the c library:
int read(int file, char *ptr, int len) :::c
{ int read(int file, char *ptr, int len)
return _syscall_read(file, ptr, len); {
} return _syscall_read(file, ptr, len);
}
Simply a wrapper for an assembly function: Simply a wrapper for an assembly function:
[global _syscall_read] :::nasm
_syscall_read: [global _syscall_read]
mov eax, SYSCALL_READ _syscall_read:
int 0x80 mov eax, SYSCALL_READ
mov [syscall_error], edx int 0x80
ret mov [syscall_error], edx
{: .lang-nasm} ret
This function puts an identifier for the system call in the `eax` This function puts an identifier for the system call in the `eax`
register and then execute the system call interrupt. 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 Of course, this can be simplified with a macro to
[global _syscall_read] :::nasm
DEF_SYSCALL(read, SYSCALL_READ) [global _syscall_read]
{: .lang-nasm} DEF_SYSCALL(read, SYSCALL_READ)
####Kernel side ####Kernel side
In the kernel, the system call is caught by the following function: In the kernel, the system call is caught by the following function:
registers_t *syscall_handler(registers_t *r) :::c
{ registers_t *syscall_handler(registers_t *r)
if(syscall_handlers[r->eax]) {
r = syscall_handlers[r->eax](r); if(syscall_handlers[r->eax])
else r = syscall_handlers[r->eax](r);
r->edx = ERR_NOSYSCALL; else
r->edx = ERR_NOSYSCALL;
return r;
} return r;
}
If the system call is registered correctly in the kernel (through the If the system call is registered correctly in the kernel (through the
macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything
onto the following function: onto the following function:
KDEF_SYSCALL(read, r) :::c
{ KDEF_SYSCALL(read, r)
process_stack stack = init_pstack(); {
process_stack stack = init_pstack();
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
r->edx = errno; r->edx = errno;
return r; return r;
} }
The `init_pstack()` macro expands to `(unitptr_t *)(r->useresp + 0x4)` 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 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. 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 _Spoiler alert:_ Keeping a version of `read()` (and in fact every
syscall function) inside the kernel will turn out to have some really 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: I'm also a fan of clean makefiles. Take a look at this:
VPATH := ../src :::make
VPATH := ../src
CC := i586-pc-myos-gcc
CC := i586-pc-myos-gcc
TARGETS := $(shell find ../src -name "*.c")
TARGETS := $(notdir $(TARGETS)) TARGETS := $(shell find ../src -name "*.c")
TARGETS := $(patsubst %.c, %, $(TARGETS)) TARGETS := $(notdir $(TARGETS))
TARGETS := $(patsubst %.c, %, $(TARGETS))
all: $(TARGETS)
all: $(TARGETS)
clean:
-rm $(TARGETS) 2>/dev/null clean:
{: .lang-make} -rm $(TARGETS) 2>/dev/null
That's the makefile for the entire `/bin` directory in my os. 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 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 name for my kernel besides an iteration number...) I did
export TARGET=i586-pc-myos :::bash
export PREFIX=/usr/local/Cellar/osdev/1.0 $ export TARGET=i586-pc-myos
# Configure, build and install binutils $ export PREFIX=/usr/local/Cellar/osdev/1.0
brew link osdev # Configure, build and install binutils
# Configure, build and install gcc and libgcc $ brew link osdev
brew unlink osdev # Configure, build and install gcc and libgcc
brew link osdev $ brew unlink osdev
$ brew link osdev
And that prepared me for building newlib. 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. you need automake version 1.12 or earlier and autoconf version 2.64.
Unfortunately, those versions are not available through Homebrew, so ... Unfortunately, those versions are not available through Homebrew, so ...
curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz :::bash
tar -zxf automake-1.12.tar.gz $ curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
mkdir -p build-automake $ tar -zxf automake-1.12.tar.gz
pushd build-automake $ mkdir -p build-automake
../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12 $ pushd build-automake
make all -j $ ../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
make install $ make all -j
popd $ make install
curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz $ popd
tar -zxf autoconf-2.64.tar.gz $ curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
pushd build-autoconf $ tar -zxf autoconf-2.64.tar.gz
../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64 $ pushd build-autoconf
make all -j $ ../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
make install $ make all -j
popd $ make install
$ popd
brew switch automake 1.12 $ brew switch automake 1.12
brew switch autoconf 2.64 $ brew switch autoconf 2.64
Those last two lines tells Homebrew that you want to use those specific Those last two lines tells Homebrew that you want to use those specific
versions for now. 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 [syscall interface](/blog/2013/06/System-Calls) i've described earlier
but I also wrapped `crt0.S` and `syscalls.c` in but I also wrapped `crt0.S` and `syscalls.c` in
#ifndef KERNEL_MODE :::c
... #ifndef KERNEL_MODE
#endif ...
#endif
Then I built it all through Then I built it all through
pushd build-newlib :::bash
../newlib/configure --target=$TARGET --prefix=$PREFIX $ pushd build-newlib
export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE $ ../newlib/configure --target=$TARGET --prefix=$PREFIX
make -j $ export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
make install $ make -j
mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a $ make install
rm -rf * $ mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
../newlib/configure --target=$TARGET --prefix=$PREFIX $ rm -rf *
export CPPFLAGS_FOR_TARGET= $ ../newlib/configure --target=$TARGET --prefix=$PREFIX
make -j $ export CPPFLAGS_FOR_TARGET=
make install $ make -j
popd $ make install
$ popd
This gives me two versions of the newlib c library. One with all syscalls 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 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: Somewhat simplified:
OBJECTS := $(shell find . -name "*.S") :::make
OBJECTS += $(shell find . -name "*.c") OBJECTS := $(shell find . -name "*.S")
OBJECTS := $(OBJECTS:%.S=%.o) OBJECTS += $(shell find . -name "*.c")
OBJECTS := $(OBJECTS:%.c=%.o) OBJECTS := $(OBJECTS:%.S=%.o)
OBJECTS := $(OBJECTS:%.c=%.o)
CC := i586-pc-myos-gcc CC := i586-pc-myos-gcc
LDFLAGS := -nostdlib -T linkfile.ld LDFLAGS := -nostdlib -T linkfile.ld
LDLIBS := -lkernel LDLIBS := -lkernel
kernel: $(OBJECTS) kernel: $(OBJECTS)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
and everything else is taken care of by the default rules of gnu make, and everything else is taken care of by the default rules of gnu make,
including preprocessing and assembling .S files. including preprocessing and assembling .S files.
For executables running under my operating system it's even easier 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. 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) [ELF specification](http://www.skyfree.org/linux/references/ELF_Format.pdf)
gives an excellent description on the meaning and use of each field. gives an excellent description on the meaning and use of each field.
typedef struct :::c
{ typedef struct
uint8_t identity[16]; {
uint16_t type; uint8_t identity[16];
uint16_t machine; uint16_t type;
uint32_t version; uint16_t machine;
uint32_t entry; uint32_t version;
uint32_t ph_offset; uint32_t entry;
uint32_t sh_offset; uint32_t ph_offset;
uint32_t flags; uint32_t sh_offset;
uint16_t header_size; uint32_t flags;
uint16_t ph_size; uint16_t header_size;
uint16_t ph_num; uint16_t ph_size;
uint16_t sh_size; uint16_t ph_num;
uint16_t sh_num; uint16_t sh_size;
uint16_t strtab_index; uint16_t sh_num;
}__attributes__((packed)) elf_header; uint16_t strtab_index;
}__attributes__((packed)) elf_header;
The first thing we should do is check whether we actually got an 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 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` `0x7F`,`'E'`,`'L'`,`'F'`. If that's correct, we can look at the `type`
field. For an executable standalone program, this should be `2`. field. For an executable standalone program, this should be `2`.
int load_elf(uint8_t *data) :::c
{ int load_elf(uint8_t *data)
elf_header *elf = (elf_header *)data; {
if(is_elf(elf) != ELF_TYPE_EXECUTABLE) elf_header *elf = (elf_header *)data;
return -1; if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
... return -1;
...
`is_elf` looks as follows. Note the use of `strncmp` which I can do `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/). because I link [newlib into my kernel](/blog/2013/08/Catching-Up/).
int is_elf(elf_header *elf) :::c
{ int is_elf(elf_header *elf)
int iself = -1; {
int iself = -1;
if((elf->identity[0] == 0x7f) && \
!strncmp((char *)&elf->identity[1], "ELF", 3)) if((elf->identity[0] == 0x7f) && \
{ !strncmp((char *)&elf->identity[1], "ELF", 3))
iself = 0; {
} iself = 0;
}
if(iself != -1)
iself = elf->type; if(iself != -1)
iself = elf->type;
return iself;
} return iself;
}
Should be pretty straight forward. Let's continue. 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 program headers which are located in a table at offset `ph_offset` in
the file. the file.
typedef struct :::c
{ typedef struct
uint32_t type; {
uint32_t offset; uint32_t type;
uint32_t virtual_address; uint32_t offset;
uint32_t physical_address; uint32_t virtual_address;
uint32_t file_size; uint32_t physical_address;
uint32_t mem_size; uint32_t file_size;
uint32_t flags; uint32_t mem_size;
uint32_t align; uint32_t flags;
}__attributes__((packed)) elf_phead; uint32_t align;
}__attributes__((packed)) elf_phead;
The program headers each tell us about one section of the file, and we 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 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 in memory. So, the next step would be to go through all program headers
looking for loadable sections and load them into memory. looking for loadable sections and load them into memory.
... :::c
elf_phead *phead = (elf_phead)&data[elf->ph_offset]; ...
uint32_t i; elf_phead *phead = (elf_phead)&data[elf->ph_offset];
for(i = 0; i < elf->ph_num; i++) uint32_t i;
{ for(i = 0; i < elf->ph_num; i++)
if(phead[i].type == ELF_PT_LOAD) {
{ if(phead[i].type == ELF_PT_LOAD)
load_elf_segment(data, &phead[i]); {
} load_elf_segment(data, &phead[i]);
} }
return 0; }
} return 0;
}
This would also be a good time to update the memory manager information 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 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 Anyway, `load_elf_segment()` looks like this
void load_elf_segment(uint8_t *data, elf_phead *phead) :::c
{ 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
uint32_t flags = MM_FLAG_READ; uint32_t memsize = phead->mem_size; // Size in memory
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE; 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, \ uint32_t flags = MM_FLAG_READ;
flags, MM_TYPE_DATA); 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); if(memsize == 0) return;
memset(mempos + filesize, 0, memsize - filesize);
} memcpy(mempos, &data[filepos], filesize);
memset(mempos + filesize, 0, memsize - filesize);
}
Let's go through it. 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 I reapplied my changes one at a time and found the line that caused the
problem. problem.
int i = 0; :::c
int i = 0;
What?? That's it? Declaring a variable? What?? That's it? Declaring a variable?

View File

@ -1,7 +1,7 @@
layout: post layout: post
title: "Virtual File System 2" title: "Virtual File System 2"
subtitle: "for real this time." subtitle: "for real this time."
tags: [osdev] tags: [osdev, filesystems]
Once again, several months have passed since I wrote anything here. 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 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. 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 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 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 them. Nor will the VFS be designed with ext2 or any other disk file
system in mind. system in mind.
The VFS should offer the functions The VFS should offer the functions
open() // Open or create a file :::c
close() // Close a file open() // Open or create a file
read() // Read data from opened file close() // Close a file
write() // Write data to opened file read() // Read data from opened file
move() // Move a file or directory write() // Write data to opened file
link() // Put a file or directory in path tree move() // Move a file or directory
unlink() // Remove a file or directory from path tree link() // Put a file or directory in path tree
stat() // Get more info about a file or directory unlink() // Remove a file or directory from path tree
isatty() // Returns true if the file is a terminal stat() // Get more info about a file or directory
mkdir() // Create a directory isatty() // Returns true if the file is a terminal
readdir() // Get a directory entry mkdir() // Create a directory
finddir() // Find a file by name from 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 for all files and directories regardless of their underlying device or
driver. driver.
@ -96,20 +97,18 @@ Example:
A good starting point for the inode structure might be some pointers to A good starting point for the inode structure might be some pointers to
allow it to be placed in a tree, then. allow it to be placed in a tree, then.
:::c
struct vfs_node_st;
typedef vfs_node_t * INODE;
struct vfs_node_st; typedef struct vfs_node_st
typedef vfs_node_t * INODE; {
char name[VFS_NAME_SZ];
typedef struct vfs_node_st INODE parent;
{ INODE child;
char name[VFS_NAME_SZ]; INODE older, younger;
INODE parent; uint32_t type;
INODE child; } vfs_node_t;
INODE older, younger;
uint32_t type;
} vfs_node_t;
This does waste a bit of memory, since most inodes that are used by the 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, 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 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: called to access the file. To do this, I define a new struct:
typedef struct vfs_driver_st :::c
{ typedef struct vfs_driver_st
uint32_t (*open)(INODE, uint32_t); {
uint32_t (*close)(INODE); uint32_t (*open)(INODE, uint32_t);
uint32_t (*read)(INODE, void *, uint32_t, uint32_t); uint32_t (*close)(INODE);
uint32_t (*write)(INODE, void *, uint32_t, uint32_t); uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
uint32_t (*link)(INODE, INODE, const char *); uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
uint32_t (*unlink)(INODE, const char *); uint32_t (*link)(INODE, INODE, const char *);
uint32_t (*stat)(INODE, struct stat *st); uint32_t (*unlink)(INODE, const char *);
uint32_t (*isatty)(INODE); uint32_t (*stat)(INODE, struct stat *st);
uint32_t (*mkdir)(INODE, const char *); uint32_t (*isatty)(INODE);
dirent_t *(*readdir)(INODE, uint32_t); uint32_t (*mkdir)(INODE, const char *);
INODE (*finddir)(INODE, const char *); dirent_t *(*readdir)(INODE, uint32_t);
} vfs_driver_t; INODE (*finddir)(INODE, const char *);
} vfs_driver_t;
and add `vfs_driver_t *d` to the inode struct. I also added a length 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, a void pointer for arbitrary data used by the drivers and a flags
value - also for use by the drivers. The value - also for use by the drivers. The
inode struct now looks like this: inode struct now looks like this:
typedef struct vfs_node_st :::c
{ typedef struct vfs_node_st
char name[VFS_NAME_SZ]; {
void *parent; char name[VFS_NAME_SZ];
void *child; void *parent;
void *older, *younger; void *child;
uint32_t type; void *older, *younger;
vfs_driver_t *d; uint32_t type;
void *data; vfs_driver_t *d;
uint32_t flags; void *data;
uint32_t length; uint32_t flags;
} uint32_t length;
}
###Vfs functions ###Vfs functions
Next, I create some wrapper functions to call the driver functions. Next, I create some wrapper functions to call the driver functions.
uint32_t vfs_open(INODE ino, uint32_t mode) :::c
{ uint32_t vfs_open(INODE ino, uint32_t mode)
if(ino->d->open) {
return ino->d->open(ino, mode); if(ino->d->open)
return 0; return ino->d->open(ino, mode);
} return 0;
}
and similar for all functions except `readdir` and `finddir` which and similar for all functions except `readdir` and `finddir` which
contain code to handle `.` and `..` for mount roots. contain code to handle `.` and `..` for mount roots.
dirent_t *vfs_readdir(INODE ino, uint32_t num) :::c
{ dirent_t *vfs_readdir(INODE ino, uint32_t num)
if(ino->type & FS_MOUNT) {
{ if(ino->type & FS_MOUNT)
if(num == 0) {
{ if(num == 0)
dirent_t *ret = calloc(1, sizeof(dirent_t)); {
ret->ino = ino; dirent_t *ret = calloc(1, sizeof(dirent_t));
strcpy(ret->name, "."); ret->ino = ino;
return ret; strcpy(ret->name, ".");
} else if(num == 1) { return ret;
dirent_t *ret = calloc(1, sizeof(dirent_t)); } else if(num == 1) {
ret->ino = ino->parent; dirent_t *ret = calloc(1, sizeof(dirent_t));
strcpy(ret->name, ".."); ret->ino = ino->parent;
return ret; strcpy(ret->name, "..");
} return ret;
} }
if(ino->d->readdir) }
return ino->d->readdir(ino, num); if(ino->d->readdir)
return 0; return ino->d->readdir(ino, num);
} return 0;
}
&nbsp; &nbsp;
INODE vfs_finddir(INODE ino, const char *name) :::c
{ INODE vfs_finddir(INODE ino, const char *name)
if(ino->type & FS_MOUNT) {
{ if(ino->type & FS_MOUNT)
if(!strcmp(name, ".")) {
{ if(!strcmp(name, "."))
return ino; {
} else if(!strcmp(name, "..")) { return ino;
return ino->parent; } else if(!strcmp(name, "..")) {
} return ino->parent;
} }
if(ino->d->finddir) }
return ino->d->finddir(ino, name); if(ino->d->finddir)
if(ino->d->readdir) return ino->d->finddir(ino, name);
{ if(ino->d->readdir)
// Backup solution {
int num = 0; // Backup solution
dirent_t *de; int num = 0;
while(1) dirent_t *de;
{ while(1)
de = vfs_readdir(ino, num); {
if(!de) de = vfs_readdir(ino, num);
return 0; if(!de)
if(!strcmp(name, de->name)) return 0;
break; if(!strcmp(name, de->name))
free(de->name); break;
free(de); free(de->name);
num++; free(de);
} num++;
INODE ret = de->ino; }
free(de->name); INODE ret = de->ino;
free(de); free(de->name);
return ret; free(de);
} return ret;
return 0; }
} return 0;
}
Finally, I needed a function for mounting filesystems in the mount tree Finally, I needed a function for mounting filesystems in the mount tree
and the `namei` function, which can actually be combined since they both 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 First: a function for traversing the mount tree as far as possible
INODE vfs_find_root(char **path) :::c
{ INODE vfs_find_root(char **path)
// Find closest point in mount tree {
INODE current = vfs_root; // Find closest point in mount tree
INODE mount = current; INODE current = vfs_root;
char *name; INODE mount = current;
while((name = strsep(path, "/"))) char *name;
{ while((name = strsep(path, "/")))
current = current->child; {
while(current) current = current->child;
{ while(current)
if(!strcmp(current->name, name)) {
{ if(!strcmp(current->name, name))
mount = current; {
break; mount = current;
} break;
current = current->olderyounger; }
} current = current->olderyounger;
if(!current) }
{ if(!current)
if(*path) {
{ if(*path)
*path = *path - 1; {
*path[0] = '/'; *path = *path - 1;
} *path[0] = '/';
*path = name; }
break; *path = name;
} break;
} }
}
return (INODE)mount;
} return (INODE)mount;
}
Pretty self explanatory. No? Well, `strsep` is a library function which 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` 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) :::c
{ INODE vfs_namei_mount(const char *path, INODE root)
char *npath = strdup(path); {
char *pth = &npath[1]; char *npath = strdup(path);
// Find closest point in mount tree char *pth = &npath[1];
INODE current = vfs_find_root(&pth); // Find closest point in mount tree
char *name; INODE current = vfs_find_root(&pth);
while(current && (name = strsep(&pth, "/"))) char *name;
{ while(current && (name = strsep(&pth, "/")))
// Go through the path {
INODE next = vfs_finddir(current, name); // Go through the path
INODE next = vfs_finddir(current, name);
if(root)
{ if(root)
// If we want to mount someting {
if(!next) // 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. // Create last part of path if it doesn't exist
if(pth) // But only if it is the last part.
return 0; if(pth)
next = calloc(1, sizeof(vfs_node_t)); return 0;
strcpy(next->name, name); next = calloc(1, sizeof(vfs_node_t));
next->type = FS_DIRECTORY; strcpy(next->name, name);
} next->type = FS_DIRECTORY;
}
// Add path to mount tree
next->parent = current; // Add path to mount tree
next->older = current->child; next->parent = current;
current->child = next; next->older = current->child;
} current->child = next;
}
if(!next)
return 0; if(!next)
if(!current->parent) return 0;
free(current); if(!current->parent)
free(current);
current = next;
} current = next;
free(npath); }
free(npath);
if(root && current->type == FS_DIRECTORY)
{ if(root && current->type == FS_DIRECTORY)
// Replace node in mount tree {
root->parent = current->parent; // Replace node in mount tree
if(root->parent->child == current) root->parent = current->parent;
root->parent->child = root; if(root->parent->child == current)
root->older = current->older; root->parent->child = root;
if(root->older) root->older = current->older;
root->older->younger = current; if(root->older)
root->younger = current->younger; root->older->younger = current;
if(root->younger) root->younger = current->younger;
root->younger->older = current; if(root->younger)
strcpy(root->name, current->name); root->younger->older = current;
root->type = FS_MOUNT; strcpy(root->name, current->name);
if(current == vfs_root) root->type = FS_MOUNT;
vfs_root = root; if(current == vfs_root)
vfs_root = root;
free(current);
} free(current);
return current; }
} return current;
{: .lang-c} }
Note how `pth` is changed by `vfs_find_root()` to only contain the part 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 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: I also made two simple wrappers for this function:
INODE vfs_namei(const char *path) :::c
{ INODE vfs_namei(const char *path)
return vfs_namei_mount(path, 0); {
} return vfs_namei_mount(path, 0);
}
INODE vfs_mount(const char *path, INODE root)
{ INODE vfs_mount(const char *path, INODE root)
return vfs_namei_mount(path, root); {
} return vfs_namei_mount(path, root);
{: .lang-c} }
And finally, a function for unmounting file systems: And finally, a function for unmounting file systems:
INODE vfs_umount(const char *path) :::c
{ INODE vfs_umount(const char *path)
char *npath = strdup(path); {
char *pth = &npath[1]; char *npath = strdup(path);
INODE ino = vfs_find_root(&pth); char *pth = &npath[1];
if(!ino || pth) INODE ino = vfs_find_root(&pth);
{ if(!ino || pth)
free(npath); {
return 0; free(npath);
} return 0;
if(ino->child) }
{ if(ino->child)
free(npath); {
return 0; free(npath);
} else { return 0;
// Remove node from mount tree } else {
if(ino->parent->child == ino) // Remove node from mount tree
ino->parent->child = ino->older; if(ino->parent->child == ino)
if(ino->younger) ino->parent->child = ino->older;
ino->younger->older = ino->older; if(ino->younger)
if(ino->older) ino->younger->older = ino->older;
ino->older->younger = ino->younger; if(ino->older)
free(npath); ino->older->younger = ino->younger;
return ino; free(npath);
} return ino;
} }
}
And that's it for now. A lot of code this time, but that's because I 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 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: Newlib requires the following syscalls:
:::c :::c
int close(int file) int close(int file)
int fstat(int file, struct stat *st) int fstat(int file, struct stat *st)
int isatty(int file) int isatty(int file)
int link(char *old, char *new) int link(char *old, char *new)
int lseek(int file, int ptr, int dir) int lseek(int file, int ptr, int dir)
int open(const char *name, int flags, int mode) int open(const char *name, int flags, int mode)
int read(int file, char *ptr, int len) int read(int file, char *ptr, int len)
int stat(const char *file, struct stat *st) int stat(const char *file, struct stat *st)
int unlink(char *name) int unlink(char *name)
int write(int file, char *ptr, int len) int write(int file, char *ptr, int len)
###open and close ###open and close
Everything starts with `open`, so let's look at that first. 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 In order to keep track of the files that are opened by a process, we
need a new data structure, though; the _file descriptor_. need a new data structure, though; the _file descriptor_.
:::c :::c
typedef struct typedef struct
{ {
INODE ino; INODE ino;
uint32_t offset; uint32_t offset;
uint32_t flags; uint32_t flags;
uint32_t users; uint32_t users;
} file_desc_t; } file_desc_t;
The file descriptor keeps track of our position in the file as well as 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 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 processes (after a `fork()` for example), and it therefore has a use
counter. Two macros are used to manipulate the use counter counter. Two macros are used to manipulate the use counter
:::c :::c
#define fd_get(fd) { (fd)->users++ } #define fd_get(fd) { (fd)->users++ }
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) } #define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
Each process descriptor has an array of pointers to file descriptors Each process descriptor has an array of pointers to file descriptors
:::c :::c
file_desc_t *fd[NUM_FILEDES]; file_desc_t *fd[NUM_FILEDES];
`open` starts by finding a free file descriptor. It then finds the file, `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: opens the file and returns the index of the file descriptor it used:
:::c :::c
int open(const char *name, int flags, int mode) int open(const char *name, int flags, int mode)
{ {
int fd; int fd;
// Find unused file descriptor // Find unused file descriptor
process_t *p = current->proc; process_t *p = current->proc;
int i; int i;
for(i=0; i < NUM_FILEDES; i++) for(i=0; i < NUM_FILEDES; i++)
{ {
if(p->fd[i]) if(p->fd[i])
continue; continue;
fd = i; fd = i;
p->fd[fd] = calloc(1, sizeof(file_desc_t)); p->fd[fd] = calloc(1, sizeof(file_desc_t));
fd_get(p->fd[fd]); fd_get(p->fd[fd]);
break; break;
} }
// Find file // Find file
INODE ino = vfs_namei(name); INODE ino = vfs_namei(name);
// Open file // Open file
vfs_open(name, flags); vfs_open(name, flags);
// Setup file descriptor // Setup file descriptor
p->fd[fd]->ino = ino; p->fd[fd]->ino = ino;
p->fd[fd]->offset = 0; p->fd[fd]->offset = 0;
p->fd[fd]->flags = flags; p->fd[fd]->flags = flags;
return fd; return fd;
} }
I stripped away all of the sanity checking and error handling code here. I stripped away all of the sanity checking and error handling code here.
With that code, the function is more than twice as long. With that code, the function is more than twice as long.
`close` is even easier: `close` is even easier:
:::c :::c
int close(int file) int close(int file)
{ {
int retval = vfs_close(p->fd[file]->ino); int retval = vfs_close(p->fd[file]->ino);
if(!p->fd[file]->ino->parent) if(!p->fd[file]->ino->parent)
free(p->fd[file]->ino); free(p->fd[file]->ino);
fd_put(p->fd[file]); fd_put(p->fd[file]);
p->fd[file] = 0; p->fd[file] = 0;
return retval; return retval;
} }
I always check if an inode has a parent before freeing it. If it has a 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. 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 It's actually really simple (excluding sanity checking and error
handling): handling):
:::c :::c
int read(int file, char *ptr, int len) int read(int file, char *ptr, int len)
{ {
process_t *p = current->proc; process_t *p = current->proc;
INODE node = p->fd[file]->ino; INODE node = p->fd[file]->ino;
int ret = vfs_read(node, ptr, len, p->fd[file]->offset); int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret; p->fd[file]->offset += ret;
return ret; return ret;
} }
Write is pretty much the same: Write is pretty much the same:
:::c :::c
int write(int file, char *ptr, int len) int write(int file, char *ptr, int len)
{ {
process_t *p = current->proc; process_t *p = current->proc;
INODE node = p->fd[file]->ino; INODE node = p->fd[file]->ino;
int ret = vfs_write(node, ptr, len, p->fd[file]->offset); int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
p->fd[file]->offset += ret; p->fd[file]->offset += ret;
return ret; return ret;
} }
###stat, fstat and isatty ###stat, fstat and isatty
`fstat` and `isatty` just passes on the information to the corresponding `fstat` and `isatty` just passes on the information to the corresponding
vfs functions: vfs functions:
:::c :::c
int fstat(int file, struct stat *st) int fstat(int file, struct stat *st)
{ {
process_t *p = current->proc; process_t *p = current->proc;
INODE node = p->fd[file]->ino; INODE node = p->fd[file]->ino;
return vfs_fstat(node, st); return vfs_fstat(node, st);
} }
:::c :::c
int isatty(int file) int isatty(int file)
{ {
process_t *p = current->proc; process_t *p = current->proc;
INODE node = p->fd[file]->ino; INODE node = p->fd[file]->ino;
return vfs_isatty(node); return vfs_isatty(node);
} }
`stat` performs a `namei` lookup to get the node instead of taking it `stat` performs a `namei` lookup to get the node instead of taking it
from the process' file descriptor table. from the process' file descriptor table.
:::c :::c
int stat(const char *file, struct stat *st) int stat(const char *file, struct stat *st)
{ {
INODE node = vfs_namei(file); INODE node = vfs_namei(file);
int retval = vfs_fstat(node, st); int retval = vfs_fstat(node, st);
if(!node->parent) if(!node->parent)
free(node); free(node);
return retval; return retval;
} }
###lseek ###lseek
The final function I'll look at now is `lseek` which sets the current The final function I'll look at now is `lseek` which sets the current
position in the file: position in the file:
:::c :::c
int lseek(int file, int ptr, int dir) int lseek(int file, int ptr, int dir)
{ {
process_t *p = current->proc; process_t *p = current->proc;
if(dir == SEEK_SET) if(dir == SEEK_SET)
{ {
p->fd[file]->offset = ptr; p->fd[file]->offset = ptr;
} }
if(dir == SEEK_CUR) if(dir == SEEK_CUR)
{ {
p->fd[file]->offset += ptr; p->fd[file]->offset += ptr;
} }
if(dir == SEEK_END) if(dir == SEEK_END)
{ {
p->fd[file]->offset = p->fd[file]->ino->length + ptr; p->fd[file]->offset = p->fd[file]->ino->length + ptr;
} }
return p->fd[file]->offset; return p->fd[file]->offset;
} }
I'll leave `link` and `unlink` for now, and come back to them when 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). I need them (i.e. I wish to implement a user writeable filesystem).

View File

@ -1,7 +1,7 @@
layout: post layout: post
title: "The debug file system" title: "The debug file system"
subtitle: "It's still in the kernel!" 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. 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 it: `stat`, `isatty` and `write`. Those are all rather simple, since
they won't need to keep track of which file we're referencing. they won't need to keep track of which file we're referencing.
uint32_t debug_stat(INODE node, struct stat *st) :::c
{ uint32_t debug_stat(INODE node, struct stat *st)
memset(st, 0, sizeof(struct stat)); {
st->st_mode = S_IFCHR; memset(st, 0, sizeof(struct stat));
return 0; st->st_mode = S_IFCHR;
} return 0;
}
I don't care much about the stat for the debug file. Maybe I'll add some I don't care much about the stat for the debug file. Maybe I'll add some
creation time or so later... creation time or so later...
uint32_t debug_isatty(INODE node) :::c
{ uint32_t debug_isatty(INODE node)
return 1; {
} return 1;
}
The debug output is a terminal. The debug output is a terminal.
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset) :::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); char *buf = calloc(size + 1, 1);
kdbg_puts(buf); memcpy(buf, buffer, size);
free(buf); kdbg_puts(buf);
return size; free(buf);
} return size;
}
`kdbg_puts` is a function I wrote `kdbg_puts` is a function I wrote
[a long time ago](/blog/2012/06/Kernel-Debug-Functions/) which prints a [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": With this, I can define a driver for the "debug device":
vfs_driver_t debug_driver = :::c
{ vfs_driver_t debug_driver =
0, // open {
0, // close 0, // open
0, // read 0, // close
debug_write, // write 0, // read
0, // link debug_write, // write
0, // unlink 0, // link
debug_stat, // stat 0, // unlink
debug_isatty, // isatty debug_stat, // stat
0, // mkdir debug_isatty, // isatty
0, // readdir 0, // mkdir
0 // finddir 0, // readdir
}; 0 // finddir
};
And then write a function to setup the device: And then write a function to setup the device:
INODE debug_dev_init() :::c
{ INODE debug_dev_init()
INODE node = calloc(1, sizeof(vfs_node_t)); {
strcpy(node->name, "debug"); INODE node = calloc(1, sizeof(vfs_node_t));
node->d = &debug_driver; strcpy(node->name, "debug");
node->type = FS_CHARDEV; node->d = &debug_driver;
return node; node->type = FS_CHARDEV;
} return node;
}
Then, to activate it, all I need to do is add Then, to activate it, all I need to do is add
vfs_init(); :::c
vfs_mount("/", debug_dev_init()); vfs_init();
vfs_mount("/", debug_dev_init());
in my kernel boot code. After that I can use the standard library in my kernel boot code. After that I can use the standard library
functions: functions:
FILE *dbg = fopen("/", "w"); :::c
fprintf(dbg, "Hello, world!\n"); FILE *dbg = fopen("/", "w");
fprintf(dbg, "Hello, world!\n");
or even: or even:
fopen("/", "r"); :::c
fopen("/", "w"); fopen("/", "r");
printf("Hello, world!\n"); fopen("/", "w");
printf("Hello, world!\n");
That's it for this time. Next time, we'll do some piping! That's it for this time. Next time, we'll do some piping!

View File

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

View File

@ -1,7 +1,7 @@
layout: post layout: post
title: "TAR Filesystem" title: "TAR Filesystem"
subtitle: "Almost a useful system" subtitle: "Almost a useful system"
tags: [osdev] tags: [osdev, filesystems]
It's finally time to implement an actuall filesystem, and all the hard It's finally time to implement an actuall filesystem, and all the hard
work with the VFS framework will pay off. 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: In the POSIX standard of 1988, the data block has the following format:
typedef struct :::c
{ typedef struct
unsigned char name[100]; {
unsigned char mode[8]; unsigned char name[100];
unsigned char uid[8]; unsigned char mode[8];
unsigned char gid[8]; unsigned char uid[8];
unsigned char size[12]; unsigned char gid[8];
unsigned char mtime[12]; unsigned char size[12];
unsigned char checksum[8]; unsigned char mtime[12];
unsigned char type[1]; unsigned char checksum[8];
unsigned char linkname[100]; unsigned char type[1];
unsigned char tar_indicator[6]; unsigned char linkname[100];
unsigned char tar_version[2]; unsigned char tar_indicator[6];
unsigned char owner[32]; unsigned char tar_version[2];
unsigned char group[32]; unsigned char owner[32];
unsigned char device_major[8]; unsigned char group[32];
unsigned char device_minor[8]; unsigned char device_major[8];
unsigned char prefix[155]; unsigned char device_minor[8];
}__attribute__((packed)) tar_header_t; unsigned char prefix[155];
}__attribute__((packed)) tar_header_t;
The data block is 500 bytes long, but the data starts 512 (one standard The data block is 500 bytes long, but the data starts 512 (one standard
disk sector) bytes after the start of the data block 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: Instead, I reshape the file list into a file tree:
tree_t *build_tar_tree(tar_header_t *tar) :::c
{ tree_t *build_tar_tree(tar_header_t *tar)
... {
while(tar->name[0]) ...
{ while(tar->name[0])
tartree_add_node(tree, tar, (char *)&tar->name); {
uint32_t size; tartree_add_node(tree, tar, (char *)&tar->name);
sscanf((char *)&tar->size, "%o", &size); uint32_t size;
tar = (tar_header_t *)((size_t)tar + size + 512); sscanf((char *)&tar->size, "%o", &size);
if((size_t)tar % 512) tar = (tar_header_t *)((size_t)tar + size + 512);
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%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)
{ void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
... {
tree_node_t *node = tree->root; ...
char *p; tree_node_t *node = tree->root;
for(p = strtok(path, "/"); p; p = strtok(NULL, "/")) char *p;
{ for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
int found = 0; {
list_t *l; int found = 0;
for_each_in_list(&node->children) list_t *l;
{ for_each_in_list(&node->children)
... {
if(!strcmp(entry->name, p)) ...
{ if(!strcmp(entry->name, p))
found = 1; {
node = tn; found = 1;
break; node = tn;
} break;
} }
if(!found) }
{ if(!found)
... {
tarfs_entry_t *n = malloc(sizeof(tar_entry_t)); ...
n->name = strdup(p); tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
n->tar = tar; n->name = strdup(p);
... n->tar = tar;
tree_make_child(node, new); ...
node = new; tree_make_child(node, new);
} node = new;
} }
} }
}
Note that this assumes that the files and directories of the tar archive 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 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 tarfs driver makes use of the data field in the vfs node to store
the tar tree. the tar tree.
INODE tarfs_init(tar_header_t *tar) :::c
{ INODE tarfs_init(tar_header_t *tar)
vfs_node_t *node = calloc(1, sizeof(vfs_node_t)); {
strcpy(node->name, "tarfs"); vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
node->d = &tarfs_driver; strcpy(node->name, "tarfs");
node->type = FS_DIRECTORY; node->d = &tarfs_driver;
node->type = FS_DIRECTORY;
tree_t *tar_tree = build_tar_tree(tar);
node->data = tar_tree->root; tree_t *tar_tree = build_tar_tree(tar);
free(tar_tree); node->data = tar_tree->root;
free(tar_tree);
return node;
} return node;
}
I then add the following to my kernel initialization function: I then add the following to my kernel initialization function:
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start); :::c
vfs_mount("/", tarfs_init(tarfs_location)); 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. 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 Finally, the tarfs driver functions. For now I only need to implement
`read()` and `finddir()`. `read()` and `finddir()`.
INODE tar_finddir(INODE dir, const char *name) :::c
{ INODE tar_finddir(INODE dir, const char *name)
tree_node_t *tn = (tree_node_t *)dir->data; {
list_t *l; tree_node_t *tn = (tree_node_t *)dir->data;
for_each_in_list(&tn->children, l) list_t *l;
{ for_each_in_list(&tn->children, l)
tree_node_t *cn = list_entry(l, tree_node_t, siblings); {
... tree_node_t *cn = list_entry(l, tree_node_t, siblings);
if(!strcmp(entry->name, name) ...
{ if(!strcmp(entry->name, name)
INODE node = calloc(1, sizeof(vfs_node_t)); {
strcpy(node->name, entry->name); INODE node = calloc(1, sizeof(vfs_node_t));
node->d = &tarfs_driver; strcpy(node->name, entry->name);
node->data = (void *)cn; node->d = &tarfs_driver;
sscanf((char *)&entry->tar->size, "%o", &node->length); node->data = (void *)cn;
if(entry->tar->type[0] == TAR_TYPE_DIR) sscanf((char *)&entry->tar->size, "%o", &node->length);
node->type = FS_DIRECTORY; if(entry->tar->type[0] == TAR_TYPE_DIR)
else node->type = FS_DIRECTORY;
node->type = FS_FILE; else
return node; node->type = FS_FILE;
} return node;
} }
return 0; }
} return 0;
}
`Finddir` allocates space for a new inode for each file that is searched `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. 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 Anyway, `finddir` also finds the right node in the tarfs tree and puts
it in the `data` field of the inode. it in the `data` field of the inode.
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset) :::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; tree_node_t *tn = (tree_node_t *)node->data;
tar_header_t *tar = te->tar; tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
tar_header_t *tar = te->tar;
uint32_t tsz;
sscanf((char *)&tar->size, "%o", &tsz); uint32_t tsz;
if(offset > tsz) return EOF; sscanf((char *)&tar->size, "%o", &tsz);
if(offset > tsz) return EOF;
if((size + offset) > tsz) size = tsz - offset;
if((size + offset) > tsz) size = tsz - offset;
offset = offset + (uint32_t)tar + 512;
memcpy(buffer, (void *)offset, size); offset = offset + (uint32_t)tar + 512;
if(size == tsz - offset) memcpy(buffer, (void *)offset, size);
((char *)buffer)[size] = EOF; if(size == tsz - offset)
((char *)buffer)[size] = EOF;
return size;
} return size;
}
###Using it ###Using it
Now, all I need to do in order to make read-only files accessible to my 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 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 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. or by adding a line to the grub `menu.lst` file.
module /boot/tarfs.tar module /boot/tarfs.tar
###Final note ###Final note
While writing this post, I got back to polishing this code and added 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 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 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: Those are:
void *sbrk(int incr); :::c
int getpid(); void *sbrk(int incr);
int fork(); int getpid();
void _exit(int rc); int fork();
int wait(int *status); void _exit(int rc);
int kill(int pid, int sig); int wait(int *status);
int execve(char *name, char **argv, char **env); int kill(int pid, int sig);
int execve(char *name, char **argv, char **env);
Let's just go through them one at a time: 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 manager](/blog/2013/06/Even-More-Memory/) to return
a chunk of new memory for the `malloc` functions. a chunk of new memory for the `malloc` functions.
void *usr_sbrk(int incr) :::c
{ void *usr_sbrk(int incr)
process_t *p = current->proc; {
mem_area_t *area = find_including(p, p->mm.data_end); process_t *p = current->proc;
if(area) mem_area_t *area = find_including(p, p->mm.data_end);
{ if(area)
if(area->end > (p->mm.data_end + incr)) {
{ if(area->end > (p->mm.data_end + incr))
// The current memory area is large enough {
} else { // The current memory area is large enough
// Increase memory area } else {
new_area(p, area->end, p->mm.data_end + incr, \ // Increase memory area
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \ new_area(p, area->end, p->mm.data_end + incr, \
MM_TYPE_DATA); MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
} MM_TYPE_DATA);
} else { }
// Create a new memory area } else {
new-area(p, p->mm.data_end, p->mm.data_end + incr, \ // Create a new memory area
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \ new-area(p, p->mm.data_end, p->mm.data_end + incr, \
MM_TYPE_DATA); 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); 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 The kernel space version is just a simple linear allocator
uintptr_t kmem_top = KERNEL_HEAP_START; :::c
uintptr_t kmem_ptr = KERNEL_HEAP_START; uintptr_t kmem_top = KERNEL_HEAP_START;
void *sbrk(int incr) uintptr_t kmem_ptr = KERNEL_HEAP_START;
{ void *sbrk(int incr)
if(kmem_ptr + incr > KERNEL_HEAP_END) {
{ if(kmem_ptr + incr > KERNEL_HEAP_END)
// PANIC! {
... // PANIC!
} ...
while(kmem_top < kmem_ptr + incr) }
{ while(kmem_top < kmem_ptr + incr)
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \ {
PAGE_PRESENT | PAGE_WRITE)); vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
kmem_top += PAGE_SIZE; PAGE_PRESENT | PAGE_WRITE));
} kmem_top += PAGE_SIZE;
kmem_ptr = kmem_ptr + incr; }
return (void *)kmem_ptr - incr; kmem_ptr = kmem_ptr + incr;
} return (void *)kmem_ptr - incr;
}
Hopefully it's obvious why the kernel one is called `sbrk` while the Hopefully it's obvious why the kernel one is called `sbrk` while the
user one has a different name. user one has a different name.
@ -79,42 +82,45 @@ user one has a different name.
###getpid ###getpid
`getpid` is rather obvious: `getpid` is rather obvious:
int getpid() :::c
{ int getpid()
return current->proc->pid; {
} return current->proc->pid;
}
###fork ###fork
`fork` clones the current process and starts a new thread of execution. `fork` clones the current process and starts a new thread of execution.
int fork() :::c
{ int fork()
process_t *child = fork_process(); {
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads); process_t *child = fork_process();
ch_thread->r.eax = 0; thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
scheduler_insert(ch_thread); ch_thread->r.eax = 0;
return child->pid; scheduler_insert(ch_thread);
} return child->pid;
}
###_exit ###_exit
`_exit` stops a program and wakes up any processes that are sleeping on `_exit` stops a program and wakes up any processes that are sleeping on
it. it.
void _exit(int rc) :::c
{ void _exit(int rc)
process_t *p = current->proc; {
process_t *p = current->proc;
// Close all open files
int i; // Close all open files
for(i = 0; i < NUM_FILEDES; i++) int i;
{ for(i = 0; i < NUM_FILEDES; i++)
if(p->fd[i]) {
close(i); if(p->fd[i])
} close(i);
exit_process(current->proc, rc); }
current->state = THREAD_STATE_FINISHED; exit_process(current->proc, rc);
schedule(); current->state = THREAD_STATE_FINISHED;
} schedule();
}
`_exit` doesn't return, and in fact `schedule()` will never return as `_exit` doesn't return, and in fact `schedule()` will never return as
far as this thread is concerned. 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 Actually, I didn't quite implement `wait` yet, but instead use
a `waitpid` for now, which is a bit more specific: a `waitpid` for now, which is a bit more specific:
int waitpid(int pid) :::c
{ int waitpid(int pid)
process_t *proc = get_process(pid); {
while(proc->state != PROC_STATE_FINISHED) process_t *proc = get_process(pid);
{ while(proc->state != PROC_STATE_FINISHED)
scheduler_sleep(current, &proc->waiting); {
schedule(); scheduler_sleep(current, &proc->waiting);
} schedule();
int ret = proc->exit_code; }
free_process(proc); int ret = proc->exit_code;
return ret; free_process(proc);
} return ret;
}
This _should_ contain a check that process `pid` is a child of the This _should_ contain a check that process `pid` is a child of the
current process too... current process too...
@ -162,104 +169,111 @@ is:
First of all, the executable is found. If it doesn't exist, we want to First of all, the executable is found. If it doesn't exist, we want to
fail as early as possible - before we destroy everything. fail as early as possible - before we destroy everything.
int execve(char *name, char **argv, char **env) :::c
{ int execve(char *name, char **argv, char **env)
INODE executable = vfs_namei(name); {
if(!executable) INODE executable = vfs_namei(name);
{ if(!executable)
errno = ENOENT; {
return -1; errno = ENOENT;
} return -1;
... }
...
The arguments and environment are null-terminated lists of strings The arguments and environment are null-terminated lists of strings
stored in user space, so they have to be copied into kernel space before stored in user space, so they have to be copied into kernel space before
the user space is destroyed: the user space is destroyed:
... :::c
usigned int envc = 0; ...
char **temp_env = 0; usigned int envc = 0;
if(env) char **temp_env = 0;
{ if(env)
while(env[envc++]); // Count number of environmental variables {
while(env[envc++]); // Count number of environmental variables
temp_env = calloc(envc, sizeof(char *));
unsigned int i = 0; temp_env = calloc(envc, sizeof(char *));
while(env[i]) unsigned int i = 0;
{ while(env[i])
temp_env[i] = strdup(env[i]); {
i++; temp_env[i] = strdup(env[i]);
} i++;
} }
}
// Do the same thing for argv
... // Do the same thing for argv
...
Next, Delete all memory from the previous executable and [load the new Next, Delete all memory from the previous executable and [load the new
one](/blog/2013/08/Loading-Elf/): one](/blog/2013/08/Loading-Elf/):
procmm_removeall(current->proc); :::c
load_elf(executable); procmm_removeall(current->proc);
current->r.eax = current->r.ebx = current->r.ecx = \ load_elf(executable);
current->r.edx = 0; 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 We need to put the arguments and environment back into the new
executable's user space, so a new stack area is created: executable's user space, so a new stack area is created:
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \ :::c
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \ new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
MM_TYPE_STACK); MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
current->kernel_thread = (registers_t *)current; MM_TYPE_STACK);
uint32_t *pos = (uint32_t *)USER_STACK_TOP; current->kernel_thread = (registers_t *)current;
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
Then, copy the environment and arguments onto the stack: Then, copy the environment and arguments onto the stack:
if(env) :::c
{ if(env)
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1; {
env = (char **)pos; pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
int i = 0; env = (char **)pos;
while(temp_env[i]) 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); pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
env[i] = (char *)pos; memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
i++; env[i] = (char *)pos;
} i++;
env[envc-1] = 0; }
} env[envc-1] = 0;
// Do the same for argc }
... // Do the same for argc
...
And finally, push the argument count, argument list and environment list And finally, push the argument count, argument list and environment list
onto the stack: onto the stack:
pos = pos - 3; :::c
pos[0] = (uint32_t)argc - 1; pos = pos - 3;
pos[1] = (uint32_t)argv; pos[0] = (uint32_t)argc - 1;
pos[2] = (uint32_t)env; 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; current->r.useresp = current->r.ebp = (uint32_t)pos;
current->r.ecx = (uint32_t)pos;
return 0;
} return 0;
}
This pushes argc, argv and env as arguments to the executabl. We can 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 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: pushes `ecx` to the stack and then calls `_init` which looks like this:
extern char **environ; :::c
void _init(uint32_t *args) extern char **environ;
{ void _init(uint32_t *args)
int argc; {
char **argv; int argc;
if(args) char **argv;
{ if(args)
argc = args[0]; {
argv = (char **)args[1]; argc = args[0];
environ = (char **)args[2]; argv = (char **)args[1];
} else {...} environ = (char **)args[2];
} else {...}
exit(main(argc, argv));
} 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, First of all, in order to compile newlib with kernel supported signals,
you need the line 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 in your entry in `newlib/configure.host` as described in [the osdev
wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling). wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
Then you need the syscalls: Then you need the syscalls:
sig_t signal(int signum, sig_t handler); :::c
int kill(int pid, int sig); sig_t signal(int signum, sig_t handler);
int kill(int pid, int sig);
###Raising signals ###Raising signals
@ -38,31 +40,33 @@ as I go along later.
The way I chose to implement signals was through a list for each 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` process. When a signal is sent using `kill` or similar, a `signal_t`
typedef struct :::c
{ typedef struct
uint32_t sig; {
uint32_t sender; uint32_t sig;
list_head_t queue; uint32_t sender;
} signal_t; list_head_t queue;
} signal_t;
is created and added to the processes list: is created and added to the processes list:
int signal_process(int pid, int signum) :::c
{ int signal_process(int pid, int signum)
process_t *p = get_process(pid); {
... process_t *p = get_process(pid);
signal_t *signal = calloc(1, sizeof(signal_t)); ...
signal->sig = signum; signal_t *signal = calloc(1, sizeof(signal_t));
signal->sender = current->proc->pid; signal->sig = signum;
init_list(signal->queue); signal->sender = current->proc->pid;
append_to_list(p->signal_queue, signal->queue); init_list(signal->queue);
append_to_list(p->signal_queue, signal->queue);
if(p == current->proc) if(p == current->proc)
{ {
handle_signals(current); handle_signals(current);
} }
return 0; return 0;
} }
If the currently running thread is in the process being signalled, we If the currently running thread is in the process being signalled, we
handle the signals immediately. Otherwise it can wait for a bit. 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` signal. Each process also contains a table of `sig_t` and the `signal`
syscall calls the following function: syscall calls the following function:
sig_t switch_handler(int signum, sig_t handler) :::c
{ sig_t switch_handler(int signum, sig_t handler)
... {
sig_t old = current->proc->signal_handler[signum]; ...
current->proc->signal_handler[signum] = handler; sig_t old = current->proc->signal_handler[signum];
return old; current->proc->signal_handler[signum] = handler;
} return old;
}
The cut out part of this function contains code to make sure the handler The cut out part of this function contains code to make sure the handler
of signal 9 (`SIGKILL`) is never changed from `SIG_DFL`. 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: I chose to hook into the my interrupt handler:
registers_t *idt_handler(registers_t *r) :::c
{ registers_t *idt_handler(registers_t *r)
... {
if(int_handlers[r->int_no) ...
{ if(int_handlers[r->int_no)
... {
registers_t *ret = int_handlers[r->int_no](r); ...
registers_t *ret = int_handlers[r->int_no](r);
if((ret->cs & 0x3) == 0x3)
{ if((ret->cs & 0x3) == 0x3)
ret = (registers_t *)handle_signals((thread_t *)ret); {
... ret = (registers_t *)handle_signals((thread_t *)ret);
} ...
... }
return ret ...
} return ret
... }
} ...
}
So, what does `handle_signals` actually do? 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 default signal handler in a table and calls it. It may be one of the
following: following:
void sighandler_ignore(int num) :::c
{ void sighandler_ignore(int num)
(void)num; {
} (void)num;
void sighandler_terminate(int num) }
{ void sighandler_terminate(int num)
fprintf(stderr,, "Process %x terminated by signal %x\n", \ {
current->proc->pid, num); fprintf(stderr,, "Process %x terminated by signal %x\n", \
_exit(num); current->proc->pid, num);
} _exit(num);
void sighandler_coredump(int num) }
{ /* Same as above */ } void sighandler_coredump(int num)
void sighandler_stop(int num) { /* Same as above */ }
{ /* Not implemented yet */ } void sighandler_stop(int num)
void sighandler_continue(int num) { /* Not implemented yet */ }
{ /* 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: If the handler is a function a new thread is created to handle it:
... :::c
sig_t handler = th->proc->signal_handler[signal->sig]; ...
thread_t *h = new_thread((void (*)(void))handler, 1); 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; append_to_list(th->proc->threads, h->process_threads);
uint32_t *stack = ((uint32_t *)th->r.useresp; h->proc = th->proc;
*--stack = signal->sig; uint32_t *stack = ((uint32_t *)th->r.useresp;
*--stack = SIGNAL_RETURN_ADDRESS; *--stack = signal->sig;
h->r.useresp = h->r.ebp = (uint32_t)stack; *--stack = SIGNAL_RETURN_ADDRESS;
remove_from_list(signal->queue); h->r.useresp = h->r.ebp = (uint32_t)stack;
free(signal); remove_from_list(signal->queue);
free(signal);
scheduler_remove(h);
scheduler_sleep(th, &h->waiting); scheduler_remove(h);
scheduler_cheat(h); scheduler_sleep(th, &h->waiting);
schedule(); scheduler_cheat(h);
} schedule();
}
This creates a new thread and pushes the signal number (as an argument) 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 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. pane which displays the debug information, but one thing at a time.
Here's what I did in a bash script: 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 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 `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: 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 which opens up another tmux pane that displays the serial output using
the auto-updating feature of `tail`. The script `colorize.sh` colorizes the auto-updating feature of `tail`. The script `colorize.sh` colorizes
certain words of the output: certain words of the output:
#!/usr/bin/env bash :::bash
#!/usr/bin/env bash
C_NO=`echo -e "\\033[0m"`
C_RED=`echo -e "\\033[31m"` C_NO=`echo -e "\\033[0m"`
C_GREEN=`echo -e "\\033[32m"` C_RED=`echo -e "\\033[31m"`
C_YELLOW=`echo -e "\\033[33m"` C_GREEN=`echo -e "\\033[32m"`
C_BLUE=`echo -e "\\033[36m"` C_YELLOW=`echo -e "\\033[33m"`
C_BLUE=`echo -e "\\033[36m"`
while read line; do
echo $line | sed \ while read line; do
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \ echo $line | sed \
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \ -e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \ -e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/" -e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
done -e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
done
Next is Next is
tmux select-pane -L :::bash
tmux split-window -v 'i586-elf-gdb' tmux select-pane -L
tmux select-pane -U tmux split-window -v 'i586-elf-gdb'
telnet localhost 4444 tmux select-pane -U
telnet localhost 4444
Which opens a new pane for the `gdb` debugger and then moves back to the Which opens a new pane for the `gdb` debugger and then moves back to the
first pane to open the telnet terminal. 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? Notice the yellow lines in that screenshot?
The ones that say The ones that say
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100 Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
(HEAD, harddrive): Ext2 unlink function. Untested. (HEAD, harddrive): Ext2 unlink function. Untested.
Kernel compilation: Mar 7 2014 14:06:27 Kernel compilation: Mar 7 2014 14:06:27
The data for this is stored in a special file called `version.c` The data for this is stored in a special file called `version.c`
#include <version.h> :::c
#include <version.h>
char * __kernel_git_hash = GITHASH;
char * __kernel_git_date = GITDATE; char * __kernel_git_hash = GITHASH;
int __kernel_git_dirty = GITDIRTY; char * __kernel_git_date = GITDATE;
char * __kernel_git_message = GITMESSAGE; int __kernel_git_dirty = GITDIRTY;
char * __kernel_git_branch = GITBRANCH; char * __kernel_git_message = GITMESSAGE;
char * __kernel_git_branch = GITBRANCH;
char * __kernel_build_date = __DATE__;
char * __kernel_build_time = __TIME__; char * __kernel_build_date = __DATE__;
char * __kernel_build_time = __TIME__;
which has a special line in the kernel makefile: which has a special line in the kernel makefile:
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \ :::make
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \ version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \ -DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \ -DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")' -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 A few more lines makes sure `version.c` is always recompiled if any
other part of the kernel is: other part of the kernel is:
kernel: $(kernel_objects) :::make
rm -f version.o kernel: $(kernel_objects)
$(MAKE) version.o rm -f version.o
$(LINK) $(MAKE) version.o
$(LINK)
Obviously, this is a bit simplified. But not much. I might make a post Obviously, this is a bit simplified. But not much. I might make a post
about my makefiles some day... about my makefiles some day...

View File

@ -1,7 +1,7 @@
layout: post layout: post
title: "DITo - Framework" title: "DITo - Framework"
subtitle: "the Disk Image TOols" subtitle: "the Disk Image TOols"
tags: [osdev] tags: [osdev, filesystems]
In my osdeving, I was starting to reach the point where a disk driver 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 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 The basic operations of DITo are reading from or writing to image files
or disk drives. Each drive type has a driver or disk drives. Each drive type has a driver
typedef struct drive_driver :::c
{ typedef struct drive_driver
int (*open)(struct drive_t *d, int flags); {
int (*close)(struct drive_t *d, int flags); int (*open)(struct drive_t *d, int flags);
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset); int (*close)(struct drive_t *d, int flags);
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset); int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
} drive_driver_t; 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 The drive type contains a pointer to the driver and a pointer to some
arbitrary data used by the driver. arbitrary data used by the driver.
typedef struct drive_t :::c
{ typedef struct drive_t
struct drive_driver *d; {
void *data; struct drive_driver *d;
} drive_t; void *data;
} drive_t;
Then there are some wrapper functions for performing the required Then there are some wrapper functions for performing the required
operations: operations:
int drive_open(struct drive_t *d, int flags) :::c
{ int drive_open(struct drive_t *d, int flags)
if(d->d->open) {
return d->d->open(d, flags); if(d->d->open)
else return d->d->open(d, flags);
return 0; else
} return 0;
}
and simmilar for `drive_close`, `drive_read` and `drive_write`. 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 thinking about it, the important primitive functions for all file
operations I could think about are all in a filesystem driver struct: operations I could think about are all in a filesystem driver struct:
struct fs_driver :::c
{ struct fs_driver
INODE (*open)(struct fs_t *fs, const char *path, int flags); {
int (*close)(struct fs_t *fs, INODE ino); INODE (*open)(struct fs_t *fs, const char *path, int flags);
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset); int (*close)(struct fs_t *fs, INODE ino);
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset); int (*read)(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 (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st); 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 (*touch)(struct fs_t *fs, const char *path, struct stat *st);
int (*unlink)(struct fs_t *fs, const char *path); int (*link)(struct fs_t *fs, const char *path1, const char *path2);
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num); 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 The `fs_t` type contains a pointer to the driver, a pointer to the drive
and a general data pointer. and a general data pointer.
typedef struct fs_t :::c
{ typedef struct fs_t
struct fs_driver *driver; {
drive_t *d; struct fs_driver *driver;
void *data; drive_t *d;
} fs_t; void *data;
} fs_t;
The wrapper functions `fs_open`, `fs_close` and so on work the same way The wrapper functions `fs_open`, `fs_close` and so on work the same way
as the `drive_*` functions. 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 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. filesystem, a unique inode number and a pointer to arbitrary data.
struct ino_st :::c
{ struct ino_st
fs_t *fs; {
unsigned int ino; fs_t *fs;
void *data; unsigned int ino;
}; void *data;
};
typedef struct ino_st * INODE;
typedef struct ino_st * INODE;
And that's the basic framework. As you probably notice, the same `fs_t` 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 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 the ext2 formated second partition of a hard drive image, I could do
someting like this: someting like this:
drive_t *fat_disk = image_drive("floppy.img"); :::c
drive_open(fat_disk, READ_FLAG); drive_t *fat_disk = image_drive("floppy.img");
drive_t *ext2_disk = image_drive("harddrive.img"); drive_open(fat_disk, READ_FLAG);
drive_open(ext2_disk, READ_WRITE_FLAG); drive_t *ext2_disk = image_drive("harddrive.img");
drive_t *ext2_partition = mbr_drive(ext2_disk, 2); drive_open(ext2_disk, READ_WRITE_FLAG);
drive_open(ext2_partition, 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); 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)); INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
fs_struct(fat, source, st); 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); fs_touch(ext2, "/new/path", st);
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
void *buffer = malloc(BUFER_SIZE);
off_t offset = 0; void *buffer = malloc(BUFER_SIZE);
off_t add = 0; off_t offset = 0;
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset)) 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_write(ext2, destination, buffer, BUFFER_SIZE, offset);
} offset += add;
}
fs_close(destination);
fs_close(source); fs_close(destination);
fs_close(source);
drive_close(ext2_partition);
drive_close(fat_disk); drive_close(ext2_partition);
drive_close(fat_disk);
Which of couse will eventually become its own tool so that the actual Which of couse will eventually become its own tool so that the actual
work the end user has to do becomes: 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 layout: post
title: "DITo - Drives" title: "DITo - Drives"
subtitle: "Exploring the MBR" subtitle: "Exploring the MBR"
tags: [osdev] tags: [osdev, filesystems]
Let's write a few drive drivers. 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 might be nice to have, and probably a pointer to the opened file. Would
hate to lose that... hate to lose that...
struct im_data :::c
{ struct im_data
char *filename; {
FILE *file; char *filename;
}; FILE *file;
};
The functions required for the driver would then make use of the The functions required for the driver would then make use of the
c standard library: c standard library:
int im_open(drive_t *d, int flags) :::c
{ int im_open(drive_t *d, int flags)
struct im_data *data = d->data; {
data->file = fopen(data->filename, flags); struct im_data *data = d->data;
return 1; data->file = fopen(data->filename, flags);
} return 1;
int im_close(drive_t *d, int flags) }
{ int im_close(drive_t *d, int flags)
struct im_data *data = d->data; {
fclose(data->file); struct im_data *data = d->data;
return 1; fclose(data->file);
} return 1;
int im_read(drive_t *d, void *buffer, size_t length, off_t offset) }
{ 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); struct im_data *data = d->data;
return fread(buffer, length, 1, data->file); 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) }
{ 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); struct im_data *data = d->data;
return fwrite(buffer, length, 1, data->file); fseek(data->file, offset, SEEK_SET);
} return fwrite(buffer, length, 1, data->file);
}
The function for setting up the drive is equally simple: The function for setting up the drive is equally simple:
drive_driver_t im_driver = :::c
{ drive_driver_t im_driver =
im_open, {
im_close, im_open,
im_read, im_close,
im_write, im_read,
}; im_write,
};
drive_t *image_drive(const char *filename)
{ 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)); 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; d->driver = &im_driver;
data->filename = strndup(filename, FILENAME_MAX_LENGTH); d->data = data;
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
return d;
} return d;
}
As always, I've stripped away all sanity checking and error handling. 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: The entries of the table has the following structure:
struct MBR :::c
{ struct MBR
uint8_t boot_indicator; {
uint8_t start_chs[3]; uint8_t boot_indicator;
uint8_t system_id; uint8_t start_chs[3];
uint8_t end_chs[3]; uint8_t system_id;
uint32_t start_lba; uint8_t end_chs[3];
uint32_t num_sectors; uint32_t start_lba;
}__attribute__((packed)); uint32_t num_sectors;
}__attribute__((packed));
_CHS_ and _LBA_ are different ways of addressing sectors of the disk. _CHS_ and _LBA_ are different ways of addressing sectors of the disk.

View File

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

View File

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

View File

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

View File

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

View File

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