Initial commit - Begun rewrite of website generator
BIN
media/2013.pdf
Normal file
BIN
media/img/bad_clock.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
media/img/bad_clock_signal1.png
Normal file
After Width: | Height: | Size: 534 KiB |
BIN
media/img/bad_clock_signal2.png
Normal file
After Width: | Height: | Size: 534 KiB |
BIN
media/img/debug_print.png
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
media/img/debug_print_full.png
Normal file
After Width: | Height: | Size: 314 KiB |
BIN
media/img/good_clock.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
media/img/good_clock_circuit.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
media/img/good_clock_signal.png
Normal file
After Width: | Height: | Size: 533 KiB |
BIN
media/img/ico_external.gif
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
media/img/mini_project_arduino_breadboard1.png
Normal file
After Width: | Height: | Size: 470 KiB |
BIN
media/img/mini_project_arduino_breadboard2.png
Normal file
After Width: | Height: | Size: 529 KiB |
BIN
media/img/mini_project_arduino_breadboard3.png
Normal file
After Width: | Height: | Size: 533 KiB |
BIN
media/img/mini_project_arduino_breadboard4.png
Normal file
After Width: | Height: | Size: 592 KiB |
BIN
media/img/mini_project_headset_adapter.png
Normal file
After Width: | Height: | Size: 575 KiB |
BIN
media/img/osdev_build_env.png
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
media/img/osdev_build_env_full.png
Normal file
After Width: | Height: | Size: 306 KiB |
BIN
media/img/pmm1.png
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
media/img/pmm1b.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
media/img/pmm2.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
media/img/pmm2b.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
media/img/pmm3.png
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
media/img/pmm3b.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
media/img/procmm1.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
media/img/procmm2.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
media/img/procmm3.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
media/img/procmm4.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
media/img/provbrev.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
media/img/thomas.png
Normal file
After Width: | Height: | Size: 722 KiB |
BIN
media/img/vmm1.png
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
media/img/vmm1b.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
media/img/vmm2.png
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
media/img/vmm2b.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
media/img/vmm3.png
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
media/img/vmm3b.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
media/img/z80-tester-clab.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
media/img/z80_pinout.png
Normal file
After Width: | Height: | Size: 419 KiB |
BIN
media/img/z80_pinout2.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
media/img/z80_running1.png
Normal file
After Width: | Height: | Size: 548 KiB |
BIN
media/img/z80_running2.png
Normal file
After Width: | Height: | Size: 551 KiB |
BIN
media/img/z80_running3.png
Normal file
After Width: | Height: | Size: 583 KiB |
BIN
media/img/z80_tester.png
Normal file
After Width: | Height: | Size: 572 KiB |
165
pages/2010-04-06-LaTeX-Letter_Template.md
Normal file
@ -0,0 +1,165 @@
|
||||
layout: post
|
||||
title: "LaTeX Letter Template"
|
||||
subtitle: "Simple and clean"
|
||||
tags: [latex]
|
||||
|
||||
I'm looking for a job for next year to get out of university for a while.
|
||||
|
||||
That means I've been writing a lot of letters of introduction lately. To
|
||||
streamline this, I created a custom LaTeX document class called
|
||||
personal\_letter.
|
||||
|
||||
The class looks as follows:
|
||||
|
||||
|
||||
%
|
||||
% 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
|
||||
\makeatletter
|
||||
|
||||
\newcommand{\adress}[1]{\def \@adress{#1}}
|
||||
\newcommand{\telephone}[1]{\def \@telephone{#1}}
|
||||
\newcommand{\email}[1]{\def \@email{#1}}
|
||||
\newcommand{\name}[1]{\def \@name{#1}}
|
||||
\newcommand{\place}[1]{\def \@place{#1}}
|
||||
%\newcommand{\date}[1]{\def \@date{#1}}
|
||||
\newcommand{\greeting}[1]{\def \@greeting{#1}}
|
||||
\newcommand{\closing}[1]{\def \@closing{#1}}
|
||||
\newcommand{\url}[1]{\def \@url{#1}}
|
||||
|
||||
\adress{}
|
||||
\telephone{}
|
||||
\email{}
|
||||
\name{}
|
||||
\place{}
|
||||
\date{}
|
||||
\greeting{}
|
||||
\closing{}
|
||||
\url{}
|
||||
|
||||
\makeatother
|
||||
|
||||
% Include usefull packages
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{fullpage}
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{setspace}
|
||||
\usepackage[swedish]{babel}
|
||||
\usepackage{longtable}
|
||||
|
||||
% Make graphics work with pdf or dvi files
|
||||
\usepackage{ifpdf}
|
||||
\ifpdf
|
||||
\usepackage[pdftex]{graphicx}
|
||||
\DeclareGraphicsExtensions{.pdf, .jpg, .tif}
|
||||
\else
|
||||
\usepackage{graphicx}
|
||||
\DeclareGraphicsExtensions{.eps, .jpg}
|
||||
\fi
|
||||
|
||||
|
||||
% Costruct heading
|
||||
\makeatletter
|
||||
\fancyfoot[LO, LE]{\@adress}
|
||||
\fancyfoot[RO, RE]{ \ifthenelse{\equal{\@telephone}{}}{}{ \@telephone \\ \@email \\ \@url }}
|
||||
\fancyfoot[C]{}
|
||||
\setlength{\headheight}{0 cm}
|
||||
\setlength{\headsep}{0 cm}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\addtolength{\textheight}{0 cm}
|
||||
\renewcommand{\footrulewidth}{0.5pt}
|
||||
\pagestyle{fancy}
|
||||
\makeatletter
|
||||
|
||||
%Body of the letter
|
||||
\makeatletter
|
||||
\newenvironment{body}% {
|
||||
\begin{quotation}
|
||||
\begin{onehalfspace}
|
||||
\setlength{\parskip}{1ex}
|
||||
|
||||
\ifthenelse{\equal{\@place}{}}{}{ \begin{flushright} \@place \\ \@date \end{flushright} }
|
||||
|
||||
\vspace{2ex}
|
||||
\bf
|
||||
\noindent
|
||||
\@greeting
|
||||
\rm
|
||||
}%
|
||||
{
|
||||
\vspace{10pt}
|
||||
\ifthenelse{\equal{\@closing}{}}{}{ \noindent \@closing \\ \noindent \@name } \end{onehalfspace} \end{quotation}
|
||||
}
|
||||
\makeatother
|
||||
|
||||
\newcommand{\titel}[1]{ \begin{centering} {\Huge #1} \end{centering} }
|
||||
|
||||
\newcommand{\rubrik}[1]{ \vspace{1.5em}
|
||||
|
||||
{\large \bf #1}
|
||||
|
||||
}
|
||||
{: .prettyprint .lang-tex}
|
||||
|
||||
Plain and simple.
|
||||
|
||||
And a usage example:
|
||||
|
||||
\documentclass{personal_letter}
|
||||
|
||||
\name{Thomas Lovén}
|
||||
\place{Göteborg}
|
||||
\date{6 april 2010}
|
||||
\adress{Thomas Lovén \\ Xxxxxxxxxxxxxxx XX \\ XXX XX Xxxxxx Xxxxxxxx}
|
||||
\telephone{+XX XX XXX XX XX}
|
||||
\email{thomasloven@gmail.com}
|
||||
\url{thomasloven.wordpress.com}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\greeting{Salvete!}
|
||||
\closing{Di vos incolumes custodiant.}
|
||||
|
||||
\begin{body}
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
|
||||
non arcu non massa accumsan tincidunt. Suspendisse non est quis
|
||||
massa sollicitudin faucibus. Quisque gravida vulputate nisi pharetra
|
||||
ultrices. Fusce tincidunt ante quis lacus adipiscing eget dictum
|
||||
justo luctus. Vivamus nec tempus diam. Vivamus rhoncus varius arcu,
|
||||
et vulputate purus aliquam eget. Cras eget suscipit lectus. Donec
|
||||
nec nulla ac urna ultricies bibendum sed vitae nibh. Suspendisse
|
||||
consectetur luctus quam eget vulputate. Pellentesque et nisl et quam
|
||||
vestibulum auctor quis et metus. Sed cursus tellus at felis lobortis
|
||||
ut tincidunt purus porttitor.
|
||||
|
||||
Donec gravida metus eu dui rutrum nec bibendum libero molestie.
|
||||
Aenean et odio massa. Donec pulvinar augue non tellus vulputate nec
|
||||
congue justo accumsan. Nam pretium sagittis dictum. Sed semper auctor
|
||||
neque in commodo. Mauris dignissim ante ac nibh pretium consequat.
|
||||
Donec orci tortor, pharetra non congue vel, ultrices sit amet lacus.
|
||||
Suspendisse a lacus nec ante venenatis bibendum vitae id dui. Nam
|
||||
semper arcu facilisis nunc euismod volutpat. Donec accumsan velit nec
|
||||
ante lacinia pulvinar. Phasellus ut varius enim. Pellentesque vel
|
||||
augue odio. Suspendisse sed nisi vel magna euismod semper. Maecenas
|
||||
erat neque, tristique id consequat id, mollis eu enim. Phasellus
|
||||
laoreet pulvinar ante accumsan posuere. Proin viverra dui id ipsum
|
||||
hendrerit non mollis mi rutrum.
|
||||
|
||||
\end{body}
|
||||
|
||||
\end{document}
|
||||
{: .prettyprint .lang-tex}
|
||||
|
||||

|
80
pages/2012-05-29-Hoby-Kernel-Restart.md
Normal file
@ -0,0 +1,80 @@
|
||||
layout: post
|
||||
title: "Restarting a hoby kernel"
|
||||
subtitle: "Once more, from the top... "
|
||||
tags: [osdev]
|
||||
|
||||
Many years ago, I got interesting in trying out Linux. Having used Windows
|
||||
since my parents started using Windows computers at work and declared our old
|
||||
Macintoshes obsolete I realized it would mean quite a change and didn't really
|
||||
want to go all in right away. Somehow I found out about VMWare Workstation - an
|
||||
Intel processor emulator made for running virtual computers within Windows and
|
||||
installed one of the first versions of Ubuntu in it.
|
||||
|
||||
I played around with Ubuntu Linux for a while, but then I started thinking
|
||||
about the computer emulator I was using and wondered whether I could make
|
||||
a program myself that would boot in it. In other words, could I write a program
|
||||
that ran by itself on a computer with no operating system?
|
||||
|
||||
The answer turned out to be yes. With a lot of help and tutorials from what
|
||||
later became [osdev.org](http://www.osdev.org) and many more that
|
||||
I unfortunately can't remember or find anymore, I wrote a small binary
|
||||
executable in Assembly that could run of the boot sector on a floppy disk.
|
||||
I was amazed! Could it really be this simple? I immediately set out to write an
|
||||
entire operating system. Nothing fancy; I just wanted to boot the computer and
|
||||
get into a desktop environment with a text editor and a compiler so I could
|
||||
keep adding to the system from within it. Shouldn't be too hard, should it?
|
||||
|
||||
Almost ten years later, I think I got the routines for basic BIOS-assisted
|
||||
screen printing down...
|
||||
|
||||
There are several reasons why things are moving so slowly. First and foremost:
|
||||
It wasn't that easy. Operating systems are advanced stuff, and combined with my
|
||||
own lack of any formal education or experience in programming or computer
|
||||
systems this means progress is slow.
|
||||
|
||||
Next is a lack of time. Back when I found VMWare Workstation and started this
|
||||
journey, I was on summer holiday from school and had all the time in the world.
|
||||
Then school started again. Then came the Christmas holiday and I realized I had
|
||||
forgotten everything. So I threw everything out and started again from scratch.
|
||||
This time I got a bit further. Then school started again.
|
||||
|
||||
And that's how it's been since then. School started, ended and begun again.
|
||||
Then I did a year in the army. Then university (engineering physics). Then
|
||||
I met a girl, got engaged, bought a house, worked for a year with project
|
||||
management, went back to university... Still, every now and then I've restarted
|
||||
development of my operating system dream.
|
||||
|
||||
So, how will I ever get out of this reset loop? Well, I've got a plan. Most
|
||||
important, I think, is to have realistic expectations. That shouldn't be as
|
||||
much a problem as it used to be. As time has passed I have grown to understand
|
||||
my limitations better than I did when I was 15. Also, my main goal of
|
||||
programming now is recreation. A moment of peace by myself when I can
|
||||
concentrate on a problem I appreciate. If I never get a stable virtual file
|
||||
system going, that's ok. If I build a working tcp-ip stack and get a web server
|
||||
running under my own operating system, that's ok too, but it's not my goal.
|
||||
Still, this doesn't mean I want to keep rewriting the code for setting up the
|
||||
Interupt Vector Table for the x-th time next time I have a week of university,
|
||||
which brings me to the next part of the plan.
|
||||
|
||||
Documentation. I'm going to restart my development efforts once again, but this
|
||||
time I plan to be more careful about documentation. My thought is that if
|
||||
I write down what I do and why, I won't have to do it again in six months but
|
||||
can just read through my old notes. Who knows; this plan might just be crazy
|
||||
enough to work.
|
||||
|
||||
I've obviously tried this before. It worked somewhat but I made a big mistake.
|
||||
I called my notes a "tutorial". I did that because I wished it to be
|
||||
a tutorial, that beginners could learn from my years of mistakes. Some people
|
||||
didn't like this and a few unnecesarily harsh comments got me of the osdev
|
||||
scene for a while. Then university started again and I was back in the loop.
|
||||
|
||||
This time I won't make that mistake. I still wish to make those notes public,
|
||||
though, because I still think that I and possibly others may benefit from them.
|
||||
I'll be clear though, that I am not a profesional programmer and have never
|
||||
pretended or wanted to be one. I appreciate feedback in the case anyone reads
|
||||
this and I can be reached through email or twitter (links below). Please try to
|
||||
stay constructive, though.
|
||||
|
||||
Anyway, this is already way more text than anyone would bother to read, so
|
||||
I guess I'll just cut it off here and we'll see where this series of notes
|
||||
finally takes us.
|
42
pages/2012-06-13-Bochs.md
Normal file
@ -0,0 +1,42 @@
|
||||
layout: post
|
||||
title: "Bochs"
|
||||
subtitle: "In two versions"
|
||||
tags: [osdev]
|
||||
|
||||
For testing out my os during development, I usually use
|
||||
[Bochs](http://bochs.sourceforge.net)
|
||||
|
||||
I also usually compile Bochs into two versions. One that has the debugger
|
||||
option enabled and one that runs entirely in a command line environment.
|
||||
|
||||
Let's go!
|
||||
|
||||
curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz
|
||||
tar -zxvf bochs-2.5.1.tar.gz
|
||||
mkdir build-bochs
|
||||
cd build-bochs
|
||||
|
||||
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --with-term
|
||||
make all
|
||||
make install
|
||||
{: .prettyprint .lan-sh}
|
||||
|
||||
And that's the terminal version. ### Important note This configure line won't
|
||||
actually work. I'll update with the new one as soon as I can.
|
||||
|
||||
Now we move this to *bochs-term* and compile the version with the debugger.
|
||||
|
||||
mv /usr/local/bin/bochs /usr/local/bin/bochs-term
|
||||
|
||||
rm *
|
||||
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --enable-debugger --enable-disasm --with-x11
|
||||
make all
|
||||
make install
|
||||
{: .prettyprint .lan-sh}
|
||||
|
||||
Finally, copy the bochs bioses to a public place
|
||||
|
||||
cp -r ../bochs-2.5.1/bios /usr/share/bochs
|
||||
{: .prettyprint .lan-sh}
|
||||
|
||||
And that's it.
|
75
pages/2012-06-13-Cross-Compiler.md
Normal file
@ -0,0 +1,75 @@
|
||||
layout: post
|
||||
title: "Cross compiler"
|
||||
subtitle: "Setting up under OSX Lion"
|
||||
tags: [osdev]
|
||||
|
||||
For simplicity, I chose to set up a so-called cross compiler for osdeving. In
|
||||
OSX Lion, this is what I did.
|
||||
|
||||
First of all, I installed Xcode from the Mac App store. Since version 4.3,
|
||||
Xcode doesn't install any command line tools anymore, so this had to be done
|
||||
manually:
|
||||
|
||||
- Open Xcode
|
||||
- Go the Preferences
|
||||
- Choose Downloads
|
||||
- Find Command Line Tools and click Install.
|
||||
|
||||
This is better than some earlier versions, though, which use a buggy
|
||||
c-compiler. An update was required on one of my computers.
|
||||
|
||||
Compiling gcc also requires the mpfr package to be installed. This I did with
|
||||
[Homebrew](http://mxcl.github.com/homebrew/).
|
||||
|
||||
brew install mpfr
|
||||
{: .prettyprint}
|
||||
|
||||
I downloaded all the sources I needed from [gnu.org](http://gnu.org).
|
||||
|
||||
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.gz
|
||||
curl -O http://ftp.gnu.org/gnu/gcc/gcc-4.6.3/gcc-core-4.6.3.tar.gz
|
||||
|
||||
curl -O http://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
|
||||
curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.tar.gz
|
||||
curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
|
||||
{: .prettyprint .lang-bash}
|
||||
|
||||
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
|
||||
*guaranteed*). Gmp, mpfr and mpc are floating point libraries that are used by
|
||||
gcc, so after extracting all archives, they are simply copied into the gcc
|
||||
source
|
||||
|
||||
mv gmp-5.0.2 gcc-4.6.3/gmp
|
||||
mv mpfr-3.1.0 gcc-4.6.3/mpfr
|
||||
mv mpc-0.9 gcc-4.6.3/mpc
|
||||
{: .prettyprint .lang-sh}
|
||||
|
||||
In order not to mess up the source, binutils and gcc were built out of tree.
|
||||
|
||||
mkdir build-binutils
|
||||
cd build-binutils
|
||||
|
||||
export PREFIX=/usr/local/cross
|
||||
export TARGET=i386-elf
|
||||
../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
|
||||
make all
|
||||
make install
|
||||
{: .prettyprint .lang-bash}
|
||||
|
||||
And the same for gcc, using the new binutils
|
||||
|
||||
cd ..
|
||||
mkdir build-gcc
|
||||
cd build-gcc
|
||||
export PATH=$PATH:$PREFIX/bin
|
||||
../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
|
||||
make all-gcc
|
||||
make install-gcc
|
||||
{: .prettyprint .lang-bash}
|
||||
|
||||
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
|
||||
manage to get it to actually compile...
|
||||
|
||||
And that's it!
|
120
pages/2012-06-14-Setting-Up.md
Normal file
@ -0,0 +1,120 @@
|
||||
layout: post
|
||||
title: "Setting up"
|
||||
subtitle: "Preparing the environment for OsDev"
|
||||
tags: [osdev]
|
||||
|
||||
So it's time to start.
|
||||
|
||||
I have previously set up a cross compiler environment on my Mac mini, and am
|
||||
now developing on it connected via ssh.
|
||||
|
||||
First point today was making a directory structure and creating a [git
|
||||
repository](http://github.com/thomasloven/os5). The directory structure starts
|
||||
off as follows
|
||||
|
||||
os5/
|
||||
|-- build/
|
||||
|-- include/
|
||||
`-- kernel/
|
||||
`-- include/
|
||||
|
||||
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
|
||||
files that were used both in the kernel and many other parts. I'll probably
|
||||
move it into some library directory later. Know what? Let's move it into
|
||||
a library directory right now...
|
||||
|
||||
os5/
|
||||
|-- build/
|
||||
|-- kernel/
|
||||
| `-- include/
|
||||
`-- library/
|
||||
`-- include/
|
||||
|
||||
The build/ directory contains some scripts needed for building and testing the
|
||||
os as well as a floppy image preinstalled with the GRUB bootloader.
|
||||
|
||||
Next, basic Makefiles were added to os5/ and kernel/. The standard procedure of
|
||||
the main makefile is to run the makefile in kernel/, copy the kernel image into
|
||||
the floppy and then run bochs-term.
|
||||
|
||||
|
||||
BUILDDIR := $(PWD)
|
||||
|
||||
PATH := /usr/local/cross/bin:$(PATH)
|
||||
DIRS := kernel
|
||||
TARGET := i386-elf
|
||||
|
||||
AS := nasm
|
||||
CC := i386-elf-gcc
|
||||
LD := i386-elf-ld
|
||||
|
||||
ASFLAGS := -f elf
|
||||
CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
|
||||
CCFLAGS += -fomit-frame-pointer -fno-asynchronous-unwind-tables
|
||||
CCFLAGS += -fno-unwind-tables -I$(BUILDDIR)/library/include
|
||||
LDFLAGS := -T $(BUILDDIR)/library/include/Link.ld
|
||||
|
||||
export BUILDDIR AS CC LD ASFLAGS CCFLAGS LDFLAGS
|
||||
|
||||
.SILENT:
|
||||
|
||||
.PHONY: $(DIRS) floppy emul
|
||||
.default: all floppy emul
|
||||
|
||||
l: all floppy emul
|
||||
|
||||
all: $(DIRS)
|
||||
|
||||
$(DIRS): force
|
||||
@echo " MAKE " $@
|
||||
@cd $@; $(MAKE) $(MFLAGS)
|
||||
|
||||
clean:
|
||||
@for DIR in $(DIRS); do echo " CLEAN " $$DIR; cd $(BUILDDIR)/$$DIR; make clean; done;
|
||||
|
||||
floppy: force
|
||||
@echo " UPDATING IMAGE"
|
||||
@build/update_image.sh
|
||||
|
||||
emul: force
|
||||
@echo " STARTING EMULATOR"
|
||||
@build/emul.sh
|
||||
|
||||
force:
|
||||
true
|
||||
{: .prettyprint}
|
||||
|
||||
The makefile in the kernel/ directory is pretty much straight forward.
|
||||
|
||||
|
||||
TARGET := kernel
|
||||
SUBDIR := kernel
|
||||
|
||||
SOURCES := kinit.o boot.o
|
||||
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s))
|
||||
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c))
|
||||
|
||||
CCFLAGS += -Iinclude
|
||||
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
|
||||
|
||||
.SUFFICES: .o .s .c
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SOURCES)
|
||||
@echo " ln " $(TARGET)
|
||||
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
|
||||
|
||||
.c.o:
|
||||
@echo " gcc " $<
|
||||
@$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
.s.o:
|
||||
@echo " nasm " $<
|
||||
@$(AS) $(ASFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
-@rm $(SOURCES) 2>/dev/null
|
||||
-@rm $(TARGET) 2>/dev/null
|
||||
{: .prettyprint}
|
237
pages/2012-06-15-Booting-Procedure.md
Normal file
@ -0,0 +1,237 @@
|
||||
layout: post
|
||||
title: "Booting Procedure"
|
||||
subtitle: "Starting up the x86"
|
||||
tags: [osdev]
|
||||
|
||||
[Previous part](/blog/2012/06/Setting-Up/)
|
||||
|
||||
To boot up the operating system kernel, I use
|
||||
[GRUB](http://www.gnu.org/software/grub/). It takes care of things like getting
|
||||
into protected mode, checking the memory and activating processor flags. It can
|
||||
also load any file you ask it to into memory - which is good, because we won't
|
||||
see a disk driver here anytime soon - before starting the loaded kernel.
|
||||
|
||||
I want to write a kernel that resides in a high part of memory (0xC0000000 and
|
||||
above) because I think it looks tidy. In order to load a high part kernel
|
||||
without paging, I use the trick described at
|
||||
[osdev.org](http://wiki.osdev.org/Higher_Half_bare_bones). This requires
|
||||
a special Linker file for the kernel.
|
||||
|
||||
*kernel/include/Link.ld*
|
||||
|
||||
ENTRY(start)
|
||||
|
||||
SECTIONS {
|
||||
. = 0xC0100000;
|
||||
.text : AT(ADDR(.text) - 0xC0000000)
|
||||
{
|
||||
code = .; _code = .; __code = .;
|
||||
*(.text)
|
||||
*(.eh_frame)
|
||||
. = ALIGN(4096);
|
||||
}
|
||||
.data : AT(ADDR(.data) - 0xC0000000)
|
||||
{
|
||||
data = .; _data = .; __data = .;
|
||||
*(.data)
|
||||
*(.rodata)
|
||||
. = ALIGN(4096);
|
||||
}
|
||||
.bss : AT(ADDR(.bss) - 0xC0000000)
|
||||
{
|
||||
bss = .; _bss = .; __bss = .;
|
||||
*(.bss)
|
||||
. = ALIGN(4096);
|
||||
}
|
||||
|
||||
_end = .;
|
||||
}
|
||||
{: .prettyprint .linenums}
|
||||
|
||||
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
|
||||
the first order of business. This is done in the assembly bootstrap.
|
||||
|
||||
*kernel/boot.s*
|
||||
|
||||
; Kernel start point
|
||||
[global start]
|
||||
start:
|
||||
cli
|
||||
|
||||
; Load page directory and enable paging
|
||||
mov ecx, BootPageDirectory - KERNEL_OFFSET
|
||||
mov cr3, ecx
|
||||
mov ecx, cr0
|
||||
or ecx, 0x80000000
|
||||
mov cr0, ecx
|
||||
lea ecx, [.higherHalf]
|
||||
jmp ecx
|
||||
|
||||
.higherHalf:
|
||||
; Load GDT
|
||||
mov ecx, gdt_ptr
|
||||
lgdt [ecx]
|
||||
|
||||
SetSegments 0x10, cx
|
||||
jmp 0x8:.gdtLoaded
|
||||
|
||||
.gdtLoaded:
|
||||
{: .prettyprint .lang-nasm .linenums:61}
|
||||
|
||||
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
|
||||
loads cx into each segment selector register. It is defined in an included file
|
||||
which also contains some constants.
|
||||
|
||||
*kernel/include/asm_macros.inc*
|
||||
|
||||
; GRUB multiboot headers
|
||||
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
|
||||
MBOOT_MEMORY_INFO_FLAG equ 1<<1
|
||||
MBOOT_HEADER_MAGIC equ 0x1BADB002
|
||||
|
||||
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
|
||||
MBOOT_HEADER_CHECKSUM equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)
|
||||
|
||||
|
||||
KERNEL_OFFSET equ 0xC0000000
|
||||
BOOT_STACK_SIZE equ 0x1FFF
|
||||
|
||||
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
|
||||
%macro SetSegments 2
|
||||
mov e%2, %1
|
||||
mov ds, %2
|
||||
mov es, %2
|
||||
mov fs, %2
|
||||
mov gs, %2
|
||||
mov ss, %2
|
||||
%endmacro
|
||||
{: .prettyprint .lang-nasm .linenums:2}
|
||||
|
||||
There are also references to some data structures, i.e. *BootPageDirectory* and
|
||||
*gdt_ptr*. Those are hardcoded in the bootstrap file.
|
||||
|
||||
*kernel/boot.s*
|
||||
|
||||
|
||||
%include "include/asm_macros.inc"
|
||||
|
||||
[bits 32]
|
||||
|
||||
section .bss
|
||||
|
||||
align 0x8
|
||||
|
||||
; Stack for booting
|
||||
[global BootStack]
|
||||
BootStackTop:
|
||||
resb BOOT_STACK_SIZE
|
||||
BootStack:
|
||||
|
||||
section .data
|
||||
|
||||
align 0x1000
|
||||
|
||||
; Page directory for booting up.
|
||||
; First four megabytes are identity mapped as well as
|
||||
; mapped to 0xC0000000
|
||||
[global BootPageDirectory]
|
||||
BootPageDirectory:
|
||||
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
||||
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
|
||||
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
||||
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
|
||||
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
|
||||
|
||||
BootPageTable:
|
||||
%assign i 0
|
||||
%rep 1024
|
||||
dd (i << 12) | 0x3
|
||||
%assign i i+1
|
||||
%endrep
|
||||
|
||||
; Hard-coded GDT.
|
||||
; GDT pointer is wrapped into the first entry
|
||||
[global gdt]
|
||||
gdt_ptr:
|
||||
gdt:
|
||||
dw 0x002F
|
||||
dd gdt
|
||||
dw 0x0000
|
||||
dd 0x0000FFFF, 0x00CF9A00
|
||||
dd 0x0000FFFF, 0x00CF9200
|
||||
dd 0x0000FFFF, 0x00CFFA00
|
||||
dd 0x0000FFFF, 0x00CFF200
|
||||
|
||||
section .text
|
||||
|
||||
align 4
|
||||
|
||||
; GRUB Multiboot data
|
||||
MultiBootHeader:
|
||||
dd MBOOT_HEADER_MAGIC
|
||||
dd MBOOT_HEADER_FLAGS
|
||||
dd MBOOT_HEADER_CHECKSUM
|
||||
{: .prettyprint .lang-nasm .linenums:2}
|
||||
|
||||
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
|
||||
0xC0000000. The rest of the page directory is cleared. The page table maps the
|
||||
first four megabytes of physical memory. The hard-coded GDT has five entries.
|
||||
The first one is always empty, so this space is used to store the gdt pointer.
|
||||
Then there's kernel code, kernel data, user code and user data. Each segment
|
||||
has base 0 and size 4 gb, so they all contain all of the virtual memory space.
|
||||
Finally, there's the multiboot header for GRUB. This has to be at the very
|
||||
start of the .data section, so it is also loaded first by the makefile.
|
||||
|
||||
The last thing we need before we can go into a higher level language is
|
||||
a stack, but let's take this opportunity to also remove the identity mapping
|
||||
from the page directory.
|
||||
|
||||
*kernel/boot.s*
|
||||
|
||||
.gdtLoaded:
|
||||
; Clear the identity mapping from the page directory
|
||||
mov edx, BootPageDirectory
|
||||
xor ecx, ecx
|
||||
mov [edx], ecx
|
||||
invlpg[0]
|
||||
|
||||
; Load a stack for booting
|
||||
mov esp, BootStack
|
||||
mov ebp, BootStack
|
||||
|
||||
; eax contains the magic number from GRUB 0x2BADB002
|
||||
push eax
|
||||
|
||||
; ebx contains the address of the Multiboot information structure
|
||||
add ebx, KERNEL_OFFSET
|
||||
push ebx
|
||||
|
||||
; Call the c function for setting up
|
||||
[extern kinit]
|
||||
call kinit
|
||||
jmp $
|
||||
{: .prettyprint .lang-nasm .linenums:83}
|
||||
|
||||
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
|
||||
structure location respectively.
|
||||
|
||||
The only thing that's left now in order to get this to run is the c stub.
|
||||
|
||||
*kernel/kinit.c*
|
||||
|
||||
|
||||
void kinit()
|
||||
{
|
||||
|
||||
}
|
||||
{: .prettyprint .linenums:2}
|
||||
|
||||
Compiling and running this through bochs, we are presented with a black and
|
||||
white screen completely void of error messages. Perfect!
|
||||
|
||||
The code up to this point can be found in my github repository.
|
||||
Commit [66dd86fc12](https://github.com/thomasloven/os5/tree/66dd86fc128e2714e4c93c73d8a0bf8542e10573)
|
63
pages/2012-06-18-C-Headers-In-Asm.md
Normal file
@ -0,0 +1,63 @@
|
||||
layout: post
|
||||
title: "C headers in Asm"
|
||||
subtitle: "Cleaning up the build chain"
|
||||
tags: [osdev]
|
||||
|
||||
Something that always annoyed me is how hard it is to synchronize constants
|
||||
between assembly and c code. In assembler, you define a constant value as
|
||||
|
||||
EXACT_PI equ 3
|
||||
{: .prettyprint .lang-nasm}
|
||||
|
||||
and in c
|
||||
|
||||
#define EXACT_PI 3
|
||||
{: .prettyprint .lang-c}
|
||||
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.
|
||||
|
||||
Normally, when you run a c compiler, it makes multiple passes over your source
|
||||
file. The first one or two times, it runs a pre-processor. The preprocessor
|
||||
checks for things like _#include_ and _#define_ and replaces macros. The next
|
||||
pass actually compiles the code. Then the compiler invokes a linker and so on.
|
||||
|
||||
What I found out today is that you can run only the preprocessor and it will
|
||||
replace all the preprocessor code and ignore the rest. In other words, you can
|
||||
use c preprocessor macros in assembler. Awesome!
|
||||
|
||||
So, how is this done?
|
||||
Well, here's a minimal (non-working) example:
|
||||
|
||||
_myAsmFile.asm_
|
||||
|
||||
#include <header.h>
|
||||
|
||||
mov eax, EXACT_PI
|
||||
{: .prettyprint .lang-nasm}
|
||||
|
||||
_include/header.h_
|
||||
|
||||
#pragma once
|
||||
|
||||
#define EXACT_PI 3
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
// This is not evaluated if header.h is included from an assembly file.
|
||||
#endif
|
||||
{: .prettyprint .lang-c}
|
||||
|
||||
This is compiled through:
|
||||
|
||||
cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
|
||||
nasm myAsmFile.s
|
||||
{: .prettyprint}
|
||||
|
||||
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
|
||||
preprocessor commands.
|
||||
|
||||
An alternative to _cpp_ is _gcc -E_. Actually, this is often exactly the same
|
||||
thing...
|
||||
|
||||
|
||||
This is implemented in git commit [742f2348ec](https://github.com/thomasloven/os5/tree/742f2348ecc58eaa8239b06c666bd8c3c539c019).
|
32
pages/2012-06-20-Kernel-Debug-Functions.md
Normal file
@ -0,0 +1,32 @@
|
||||
layout: post
|
||||
title: "Kernel debug functions"
|
||||
subtitle: "Can't live without them"
|
||||
tags: [osdev]
|
||||
|
||||
I read through the comments on my old OsDev series the other day and got stuck
|
||||
on one of them.
|
||||
|
||||
> "\[...\] I noticed that you handle printing in the kernel, of all places. A kernel isn't supposed to do video output.\[...\]"
|
||||
|
||||
I totally and whole-heartedly agree with this, and I really really wish I could
|
||||
code so well I didn't feel a need for outputting a single debug message before
|
||||
I had finished the booting procedure, physical and virtual memory management,
|
||||
interrupts, faults, thread and process handling, scheduling, system calls,
|
||||
message passing, virtual file system, a device driver framework, file writing
|
||||
library functions and a terminal driver. Alas, I am not that good, and
|
||||
therefore I find it helpful to print a message to the screen every now and
|
||||
then. Wheter the kernel's supposed to do video ouptut depends on the kind of
|
||||
kernel anyway.
|
||||
|
||||
But enough with this immature passive aggressiveness. Let's do some kernel
|
||||
handled printing!
|
||||
|
||||
Screen printing is usually quite simple. Most personal x86-based computers can
|
||||
do it by simply writing the text to a certain area in memory. I'm not going to
|
||||
waste very much time on it but pretty much just copy the code from an earlier
|
||||
project and move on to more interesting things.
|
||||
|
||||
I also added some useful library functions and macros like min(a,b), max(a,b)
|
||||
and swap(a,b) and a few parts of the c standard library. It can all be found
|
||||
in Git commit
|
||||
[16fdacb89](https://github.com/thomasloven/os5/tree/164fdacb896b3427633433f97bbd12d779a3d1f3).
|
72
pages/2012-06-26-Memory-Page-Stack.md
Normal file
@ -0,0 +1,72 @@
|
||||
layout: post
|
||||
title: "Memory Page Stack"
|
||||
subtitle: "Basic memory management 1/3"
|
||||
tags: [osdev]
|
||||
|
||||
###Memory management
|
||||
One of the most important tasks that the operating system has it to distribute
|
||||
the resources of the computer. One of those are the memory.
|
||||
|
||||
I like to divide the memory management of the kernel into three main parts:
|
||||
|
||||
- Physical memory management - Keeps track of the pages of actual memory
|
||||
- Virtual memory management - Keeps track of the virtual address spaces
|
||||
- Heap management - Fine grained control of memory for kernel use only
|
||||
|
||||
This post is about a part of the physical memory manager. This simple version
|
||||
keeps track of free pages of physical memory. When asked for a page, it hands
|
||||
it out and forgets about it until it is handed back.
|
||||
|
||||
###Memory page stack
|
||||
Pointers to the unused memory pages are stored on a stack and just popped off
|
||||
when needed.
|
||||
|
||||
The stack does of course take up an amount of memory, but through an
|
||||
interesting trick, this doesn't matter. You see, the memory where the stack is
|
||||
stored is made up of memory pages and if enough pages are handed out to clear
|
||||
an entire page in the stack, that page is next to be handed out.
|
||||
|
||||
Allow me to illustrate:
|
||||
Imagine a computer with really really small memory pages - so small in fact
|
||||
that each page only has room for four pointers. The figure below shows eight
|
||||
physical pages of a such computer. The two leftmost pages are used by the
|
||||
physical memory manager for the free page stack. The stack contains pointers to
|
||||
the next five pages, who are free. The rightmost page is handed out.
|
||||
{: .noborder .center}
|
||||
|
||||
When the _pmm_ receives a request for a memory page it will pop the topmost
|
||||
entry from the stack and returns, in this case, the second rightmost page to
|
||||
the caller. {: .noborder .center}
|
||||
|
||||
The next time the pmm receives a request for a memory page it will notice that
|
||||
an entire page of the stack is empty and just being wasted, so it will shrink
|
||||
its stack by one page-size and return the address of the page that previously
|
||||
made up the top of the stack. {: .noborder .center}
|
||||
|
||||
Likewise, if the stack is full of pointers when a used page is handed back,
|
||||
that page is used to increase the stack space. Through the use of virtual
|
||||
memory and paging, the stack doesn't have to be contiguous in physical memory,
|
||||
so any page can make up a part of the stack.
|
||||
|
||||
###Filling the stack
|
||||
When the computer is booted up, the bootloader is asked to give the kernel
|
||||
a map of the available memory. This is used to initialize the stack. The kernel
|
||||
simply goes through the memory map returns each unused page it finds to the
|
||||
pmm.
|
||||
|
||||
###Improvements
|
||||
There's a lot more that can be done by the pmm. One major feature that will be
|
||||
added at a later state is keeping track of how many users each page has. This
|
||||
is useful if we wish to introduce shared memory or copy-on-write during forks.
|
||||
Otherwise we risk that a page is returned and handed out again while someone
|
||||
still think they have exclusive access to it. Not a good thing.
|
||||
|
||||
###Git
|
||||
This page stack has been implemented in Git commit
|
||||
[caed8cb8a0](https://github.com/thomasloven/os5/tree/caed8cb8a0e39a1e7d7d2594b86f25b887afab81).
|
||||
Also implemented in the same commit is a virtual memory manager with recursive
|
||||
page directories described in [this
|
||||
post](/blog/2012/06/Recursive-Page-Directory).
|
||||
|
||||
__Note__: There's a bug in that commit. In short, when filling the stack, the pmm looks for memory map entries between `assert_higher(mmap_start)` and `mmap_start + mmap_size`. Where the `assert_higher` macro pretty much just adds `0xC0000000` to the adress. Well... I'm sure you see what the problem is.
|
||||
The bug has been rectified in Git commit [ebaae73](https://github.com/thomasloven/os5/tree/ebaae7383bbadbfc3de62b1b14aa9a450d8e695c).
|
109
pages/2012-06-26-Recursive-Page-Directory.md
Normal file
@ -0,0 +1,109 @@
|
||||
layout: post
|
||||
title: "Recursive Page Directory"
|
||||
subtitle: "Basic memory management 2/3"
|
||||
tags: [osdev]
|
||||
|
||||
###Memory management part 2
|
||||
As discussed in [Memory Page Stack](/blog/2012/06/Memory-Page-Stack) one can
|
||||
divide memory management into three parts.
|
||||
|
||||
This post regards the second part, the Virtual Memory Manager (VMM).
|
||||
|
||||
###Paging
|
||||
In the x86 architecture, one normally uses a two-step paging algorithm.
|
||||
|
||||
In the [previous post](/blog/2012/06/Memory-Page-Stack) we imagined a computer
|
||||
with really really small memory pages. Now imagine that this computer has an
|
||||
even equally small virtual memory space - 16 pages in total. Those 16 pages are
|
||||
divided into four groups and this is our key to addressing them.
|
||||
|
||||
{: .center .noborder}
|
||||
|
||||
Above, we see the address space of our imagined computer illustrated as 16 blue
|
||||
squares. Let's say the processor wishes to access the sixth page and that
|
||||
paging is enabled. The sixth page belongs to the second group of pages and is
|
||||
the second page in that group.
|
||||
|
||||
Now the Memory Management Unit (MMU) of the processor kicks in and takes a look
|
||||
in a certain processor register. This register contains the (physical) address
|
||||
of the Page Directory (step I). Since we want the second group, it reads the
|
||||
second entry from the page directory and this gives the physical address of
|
||||
a page table (step II). We want the second page in that group, so the MMU reads
|
||||
the second entry in the page table and this gives the physical address of the
|
||||
memory page we want (step III).
|
||||
|
||||
What makes paging so great is that the whole process is completely transparent
|
||||
to the processor. The user will never know when a memory read or write
|
||||
operation crosses a page boundary, and by changing the entries in the page
|
||||
directory and tables you can get a contiguous memory area anywhere in the
|
||||
virtual address space without having to worry about fragmentation of the
|
||||
physical memory.
|
||||
|
||||
###Recursive page directory
|
||||
Paging lets you easily decide what parts of memory the processor has access to
|
||||
and it can be dynamically changed by changing the contents of the page
|
||||
directory and page tables. Of course, that requires you to keep track of where
|
||||
those are, but a neat little trick makes that really easy.
|
||||
|
||||
The trick consists of setting the last entry in the page directory to point to
|
||||
the page directory itself.
|
||||
|
||||
Let's say we did this, and that the processor wishes to access the 14th page in
|
||||
virtual memory - that is the second page in the fourth group. The MMU starts by
|
||||
looking up the fourth group in the page directory. It finds the address of the
|
||||
page directory and assumes this is the page table for the group. It caries on,
|
||||
looking up the second entry in this 'page table' and gets the address of the
|
||||
page it wants. This happens to be the address of the page table for the second
|
||||
group. {: .center .noborder}
|
||||
|
||||
In other words, you can access any page table through a fixed address in
|
||||
memory. But wait, it gets even better.
|
||||
|
||||
Let's look at what happens if we try to access the very last page in the
|
||||
virtual memory. The MMU will look up the last entry in the page directory and
|
||||
get the address of the page directory. It will then look up the last entry in
|
||||
the page directory and get the address of the page directory (that's not a typo
|
||||
- I meant to write the same thing twice). This lets you access the page
|
||||
directory too through a fixed memory address. {: .center .noborder}
|
||||
|
||||
###Some considerations
|
||||
An important question to put at this point is whether a recursive page
|
||||
directory is really a good idea.
|
||||
|
||||
In our imaginary computer with its really small address space, we notice that
|
||||
the page table and directories now reserve a quarter of the entire available
|
||||
virtual memory - which is of course incredibly wasteful. On a computer with
|
||||
a 32 bit address bus the reserved area would be 1/1024 th of the available
|
||||
address space, though, which is more reasonable. Then again, if your computer
|
||||
has 4 gigabytes of physical RAM, this would mean there is four megabytes of it
|
||||
that can't be used. Then again again, if you have easy access to your page
|
||||
tables - such as through a recursive page directory - you can just page in
|
||||
those 4 megabytes as needed.
|
||||
|
||||
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
|
||||
a simple job to page in another table anywhere and read it.
|
||||
|
||||
The recursive page directory divides the OsDev community. I think it's a nice
|
||||
tool, others don't.
|
||||
|
||||
###The addresses
|
||||
Finally, if a recursive page directory is used on an x86, the following can be
|
||||
used to access the page directories and tables:
|
||||
|
||||
uint32_t *page_dir = 0xFFFFF000;
|
||||
uint32_t *page_tables = 0xFFC00000;
|
||||
|
||||
//addr = virtual address
|
||||
//phys = physical address (page alligned)
|
||||
//flags = access flags
|
||||
|
||||
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
|
||||
page_tables[addr >> 12] = phys | flags;
|
||||
{:.prettyprint}
|
||||
|
||||
###Git
|
||||
A recursive page directory has been implemented in Git commit
|
||||
[caed8cb8a0](https://github.com/thomasloven/os5/tree/caed8cb8a0e39a1e7d7d2594b86f25b887afab81).
|
||||
Also implemented in the same commit is a physical memory manager with a free
|
||||
page stack described in [this post](/blog/2012/06/Memory-Page-Stack)
|
110
pages/2012-07-02-Memory-Heap.md
Normal file
@ -0,0 +1,110 @@
|
||||
layout: post
|
||||
title: "Memory Heap"
|
||||
subtitle: "Basic memory management 3/3"
|
||||
tags: [osdev]
|
||||
|
||||
###Memory management part 3
|
||||
I have previously ([Memory Page Stack](/blog/2012/06/Memory-Page-Stack),
|
||||
[Recursive Page Directory](/blog/2012/06/Recursive-Page-Directory)) described
|
||||
how I like to divide the memory management of a kernel into three parts. This
|
||||
post regards the third part, which I like to call the heap.
|
||||
|
||||
###Memory Management
|
||||
In fact, a memory heap is but one way of handling memory. I believe it's the
|
||||
kind that is easiest to implement and understand (with one exception) but it is
|
||||
a bit slow.
|
||||
|
||||
The main task of a memory manager is to reserve and hand out areas in memory
|
||||
when asked. For example, a program may run _malloc(size)_ and get a pointer to
|
||||
a memory area of the asked size. It can then do whatever it wants with this
|
||||
memory area and can be sure that it will not be handed out again by _malloc_
|
||||
before it has been returned by _free_.
|
||||
|
||||
Now, let's look at a few ways to do this.
|
||||
|
||||
###Linear allocation
|
||||
The simplest possible memory manager just hands out a new address each time
|
||||
_malloc_ is called and doesn't care when memory is freed.
|
||||
|
||||
uint32_t memory_pointer = HEAP_START;
|
||||
|
||||
void *malloc(uint32_t size);
|
||||
{
|
||||
memory_pointer = memory_pointer + size;
|
||||
return memory_pointer - size;
|
||||
}
|
||||
|
||||
void free(void *mem)
|
||||
{
|
||||
;
|
||||
}
|
||||
{: .prettyprint}
|
||||
|
||||
###Heap
|
||||
The next method - which I prefer - is a memory heap.
|
||||
|
||||
The heap consists of a list of free memory areas. In the simplest possible
|
||||
variety, it would look something like this:
|
||||
|
||||
struct area_header
|
||||
{
|
||||
uint32_t size;
|
||||
struct free_area *next;
|
||||
};
|
||||
|
||||
void *malloc(uint32_t size)
|
||||
{
|
||||
struct area_header *area = heap_list_head;
|
||||
while(area)
|
||||
{
|
||||
if(area->size >= size)
|
||||
{
|
||||
remove_from_heap_list(area);
|
||||
return get_memory_area(area);
|
||||
}
|
||||
area = area->next;
|
||||
}
|
||||
panic("Out of memory!");
|
||||
}
|
||||
|
||||
void free(void *mem)
|
||||
{
|
||||
struct area_header area = get_area_header(mem);
|
||||
insert_into_heap_list(area);
|
||||
}
|
||||
{: .prettyprint}
|
||||
|
||||
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
|
||||
memory area is allocated, the caller of _malloc_ gets a pointer to the end of
|
||||
the area header. The area header thus remains untouched by the program until
|
||||
the memory is freed again.
|
||||
|
||||
The memory could then look something like below.
|
||||
|
||||
Blue areas in the figure are free and red ones are allocated. The header of
|
||||
each free memory area has a pointer to the next free area.
|
||||
|
||||
###Improvements
|
||||
Some improvements can be made to this simple scheme. First, if no free area is
|
||||
big enough, the heap can be increased by adding a new area with the right size
|
||||
to the end. Alternatively, two free areas that are right next to each other
|
||||
|
||||
can be joined together to form a bigger one.
|
||||
|
||||
Also, it would be a good idea if an area is bigger than necessary to split it.
|
||||
Finally, it's a lot easier to keep track of everything if the memory areas are
|
||||
doubly linked and if allocated areas are not removed from the list but just
|
||||
marked as used.
|
||||
|
||||
###Git
|
||||
With those improvements the scheme is similar to what I've implemented in Git
|
||||
commit
|
||||
[8db335ce3b](https://github.com/thomasloven/os5/tree/8db335ce3bed30c8e75275c2fc96a2b697106023).
|
||||
|
||||
###Further improvements
|
||||
Some more improvements can be made to the heap in order to increase
|
||||
performance. The most common ones use different ways to search for free areas
|
||||
of the correct size. For example, the free areas could be stored in an ordered
|
||||
list starting with the smallest, or they could be stored in several lists
|
||||
depending on their size.
|
104
pages/2012-07-03-Vim-Macros.md
Normal file
@ -0,0 +1,104 @@
|
||||
layout: post
|
||||
title: "Vim Macros"
|
||||
subtitle: "and interrupt handling"
|
||||
tags: [osdev]
|
||||
|
||||
###The problem
|
||||
Today I was writing some code for handling interrupts.
|
||||
At one point I needed the following piece of code
|
||||
|
||||
extern void isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void), isr47(void);
|
||||
{: .prettyprint}
|
||||
|
||||
###The solution
|
||||
Vim macros.
|
||||
|
||||
I've been using this site and my rewrite of my operating system as an excuse to
|
||||
learn vim. And today it payed off. To write the above piece of code I used the
|
||||
key presses
|
||||
|
||||
iisr0(void),<esc>0qayyp3l<ctrl>a0q46@a47k48J$r;Iextern void <esc>
|
||||
|
||||
Couldn't be easier!
|
||||
|
||||
OK, so maybe it could... Let's break it down.
|
||||
|
||||
Let's start with
|
||||
|
||||
iisr0(void),<esc>
|
||||
|
||||
_i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave
|
||||
Insert mode with the escape key.
|
||||
|
||||
Next is _0_ to bring the pointer to the beginning of the line. Then comes the
|
||||
macro.
|
||||
|
||||
qayyp3l<ctrl>a0q
|
||||
|
||||
_qa_ starts recording a macro into register a.
|
||||
|
||||
_yyp_ yanks the current line and pastes it below.
|
||||
|
||||
_3l_ skips over the i, s and r.
|
||||
|
||||
Ctrl+a increases the number under the pointer by one.
|
||||
|
||||
Finally _0_ goes back to the beginning of the line and _q_ stops the macro
|
||||
recording.
|
||||
|
||||
The next part:
|
||||
|
||||
46@a47k48J
|
||||
|
||||
runs the macro 46 times, steps up 47 times and joins the current line with the
|
||||
next 48 times. We now have
|
||||
|
||||
isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void),
|
||||
{: .prettyprint}
|
||||
|
||||
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_.
|
||||
|
||||
###Another example of macros
|
||||
|
||||
Starting with
|
||||
|
||||
INTNOERR 0
|
||||
{: .prettyprint}
|
||||
|
||||
I used
|
||||
|
||||
qayypcwINTNOERR<esc>$<ctrl>a0q
|
||||
qsyypcwINTERR<esc>$<ctrl>a0q
|
||||
dd6@a@s@a5@s33@a
|
||||
|
||||
and ended up with
|
||||
|
||||
INTNOERR 0
|
||||
INTNOERR 1
|
||||
INTNOERR 2
|
||||
INTNOERR 3
|
||||
INTNOERR 4
|
||||
INTNOERR 5
|
||||
INTNOERR 6
|
||||
INTNOERR 7
|
||||
INTERR 8
|
||||
INTNOERR 9
|
||||
INTERR 10
|
||||
INTERR 11
|
||||
INTERR 12
|
||||
INTERR 13
|
||||
INTERR 14
|
||||
INTNOERR 15
|
||||
...
|
||||
INTNOERR 45
|
||||
INTNOERR 46
|
||||
{: .prettyprint}
|
||||
|
||||
I love vim!
|
||||
|
||||
###Application
|
||||
So where did I use this? I've been writing some code for handling interrupts in
|
||||
the os. You can find it in Git commit
|
||||
[26dd8e4c75](https://github.com/thomasloven/os5/tree/26dd8e4c7507b66e4f94bf2c4e980265c6f0a20b).
|
||||
|
80
pages/2012-07-12-IPhoto-On-Older-Idevices.md
Normal file
@ -0,0 +1,80 @@
|
||||
layout: post
|
||||
title: "iPhoto on Older iDevices"
|
||||
subtitle: "Front-Facing camera? Pfft!"
|
||||
|
||||
###iPhoto for iOS
|
||||
In the shadow of the new iPad, Apple finally released a version of iPhoto for
|
||||
iOS. The problem is it's only for iPhone4, iPad2 and later. I own an iPhone3GS
|
||||
and a first generation iPad, and when I try to install it on them, they tell me
|
||||
that it requires a front-facing camera.
|
||||
|
||||
The front-facing camera requirement is of course absolute BS. The real
|
||||
restriction is the RAM of the device. iPhone 4 and iPad2 has 512 MB of RAM,
|
||||
twice as much as the 3GS and the original iPad. I assume that the camera
|
||||
restriction is just something that was in the app store already, and that's why
|
||||
Apple used it.
|
||||
|
||||
###Common solution
|
||||
It didn't take long for someone to find out that the camera restriction could
|
||||
be circumvented if you buy the app in iTunes on your computer and then install
|
||||
it using Apples own _iPhone Configuration Utility_. This does work, and iPhoto
|
||||
runs great and surprisingly smoothly on both the original iPad and the 3GS
|
||||
(though with occasional crashes due to lack of memory).
|
||||
|
||||
The problems turn up when you try to sync with iTunes. A dialog box will pop up
|
||||
asking you if you want to authorize your device with iTunes (or something to
|
||||
the effect). Selecting No will remove iPhoto and all your work. Some people got
|
||||
it to work when selecting Yes, but I never did. Instead syncing just stops.
|
||||
|
||||
###My solution (requires jailbreak)
|
||||
The key to my solution is the error message
|
||||
>This app requires a front facing camera.
|
||||
|
||||
To find out whether the device has a front facing camera or not it looks into
|
||||
the property list of _Springboard_-the main interface of iOS. This is located
|
||||
at
|
||||
|
||||
/System/Library/CoreServices/Springboard.app/
|
||||
|
||||
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
|
||||
it looks for a property called _front-facing-camera_ and if it exists and is
|
||||
set to true, it decides that your device has a front facing camera.
|
||||
|
||||
There are no further checks, and iPhoto doesn't use the camera at all, so all
|
||||
you need to do in order to install it is add this value to your property list.
|
||||
|
||||
I've had no problems synchronizing to iTunes after using this method. iPhoto
|
||||
does, however, crash at some occasions. The crashes are few and far between,
|
||||
though.
|
||||
|
||||
###A problem
|
||||
|
||||
I have found one problem with this method; the iphone 3GS thinks it has a front
|
||||
facing camera. That means there's a button in the camera app that lets you
|
||||
switch between the front and back camera. Tapping it will make the camera
|
||||
screen freeze. The remedy for this is to toggle video mode, and then you can
|
||||
switch back to the back camera.
|
||||
|
||||
###How to do it
|
||||
|
||||
- Jailbreak
|
||||
- Install iFile from Cydia
|
||||
- in iFile, navigate to _/System/Library/CoreServices/SpringBoard.app/_
|
||||
- Find your property list (N??AP.plist or K??AP.plist)
|
||||
- Open it with the Property List Viewer
|
||||
- Tap Capabilities
|
||||
- Tap the + in the bottom right corner
|
||||
- type _front-facing-camera_
|
||||
- Select Type:Boolean
|
||||
- Tap Create
|
||||
- Find your new property and activate it
|
||||
- tap Done
|
||||
- Restart springboard or your device
|
||||
|
||||
You should now be able to install iPhoto through the app store or iTunes.
|
||||
|
||||
###Bonus
|
||||
While you're in there, adding the property _screen-mirroring_ and enabling it
|
||||
will let you use official 20-pin-to-vga adaptors...
|
||||
|
158
pages/2012-07-30-Privilege-Levels.md
Normal file
@ -0,0 +1,158 @@
|
||||
layout: post
|
||||
title: "Privilege Levels"
|
||||
subtitle: "Lots of abbreviations ending in PL"
|
||||
tags: [osdev]
|
||||
|
||||
###Processor privilege level in Segmentation
|
||||
|
||||
The Intel x86 processor architecture has a number of features implemented to
|
||||
protect the system from malicious code. One of those features is the
|
||||
__Privilege Levels__.
|
||||
|
||||
The privilege levels are a remnant of the times when memory segmentation was
|
||||
popular. With segmentation, the physical memory is divided into segments that
|
||||
work as a kind of translation table. In Protected mode, if you call an address
|
||||
like
|
||||
|
||||
jmp CS:AX
|
||||
{: .prettyprint .lang-nasm}
|
||||
|
||||
the processor looks into the currently loaded __Local__ or __Global Descriptor
|
||||
Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or
|
||||
__Segment Descriptor__) describes the beginning of a segment which is combined
|
||||
with the offset in _AX_ to get the physical address;
|
||||
|
||||
physical_address = segment_descriptor_from_index(CS).base + AX;
|
||||
{: .prettyprint}
|
||||
|
||||
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__
|
||||
(or segfault for short). Now you can start to see how this system makes for
|
||||
a working memory protection scheme. By switching out the LDT, you can change
|
||||
what part of physical memory is addressed by any Selector:Offset-pair and thus
|
||||
give each task or process their own address space.
|
||||
|
||||
The segmentation scheme is now deprecated in favor of paging which offers more
|
||||
fine-grained control and a greater level or transparency.
|
||||
|
||||
So, what about the privilege levels?
|
||||
Well, the user program can switch its own segment selector values. However,
|
||||
each segment has a protection level, given by the __Descriptor Privilege
|
||||
Level__ ( __DLP__) in the segment descriptor. The processor has a __Current
|
||||
Privilege Level__ ( __CPL__) which is given by the lowest two bits of _CS_. If
|
||||
the program tries to switch a selector to a descriptor with a DPL that is lower
|
||||
than the CPL, the processor throws a __General Protection Fault__.
|
||||
|
||||
###Processor privilege levels today
|
||||
|
||||
I mentioned that segmentation is deprecated in favor of paging, so why would
|
||||
I care about it for a modern state-of-the-art operating system such as mine?
|
||||
|
||||
Firstly, the x86 architecture requires segmentation to access the entire
|
||||
address space - most hobby OSes I've studied just keeps two segments (one for
|
||||
code and one for data - processor requirement) for this, with base 0x0 and
|
||||
a limit of 4 gb (in other words, they each cover the entire virtual address
|
||||
space).
|
||||
|
||||
Secondly, there are other ways than segmentation where the CPL comes into play.
|
||||
For example, in paging, if the supervisor bit of a page table entry is set, the
|
||||
address can only be accessed if the processor is in CPL 0 (sometimes called
|
||||
__ring 0__).
|
||||
|
||||
The privilege levels are also used to determine whether certain instructions
|
||||
may be run, like _sti_, _lgdt_, _hlt_ and such.
|
||||
|
||||
Finally, the privilege levels determine which interrupts may be called with the
|
||||
_int_ instruction (each interrupt descriptor in the IDT has an assigned DPL).
|
||||
|
||||
So there's still a point to keep privilege levels around for your hobby OS,
|
||||
despite the problems they cause with segmentation and TSS and stuff.
|
||||
|
||||
###Changing the privilege level
|
||||
|
||||
Changing the CPL is actually two different problems.
|
||||
- Increasing CPL
|
||||
- Decreasing CPL
|
||||
|
||||
Increasing the CPL is relatively easy. It can be done either through a far jump
|
||||
|
||||
JMP 0x1B:label
|
||||
label:
|
||||
; The CS selector is now 0x18 | 0x3
|
||||
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
|
||||
{: .prettyprint .lang-nasm}
|
||||
|
||||
or through the `IRET` instruction
|
||||
|
||||
###The IRET instruction
|
||||
|
||||
Let's change the topic for a minute and think about interrupts.
|
||||
Say the processor is running in __Kernel Mode__ (Ring 0, CPL=0) and an
|
||||
interrupt happens. What the processor does then is:
|
||||
- Push SS and ESP to stack
|
||||
- Push EFLAGS to stack
|
||||
- Push CS and EIP to stack
|
||||
- Load CS and EIP from the IDT
|
||||
and from there the interrupt handling routine takes over.
|
||||
|
||||
The interrupt handling routine does its thing and then runs the `IRET`
|
||||
instruction. `IRET` makes the processor do the same thing as when an interrupt
|
||||
happens, but _backwards_. I.e:
|
||||
- Pop CS and EIP from stack
|
||||
- Pop EFLAGS from stack
|
||||
- Pop SS and ESP from stack
|
||||
- Do stack stuff
|
||||
- Far jump to CS:EIP
|
||||
|
||||
Notice that extra thing there? The "Do stack stuff"?
|
||||
|
||||
At that point, the processor checks the value of CS that is just popped. It
|
||||
compares the __Requested Privilege Level__ ( __RPL__, last one - promise - I'm
|
||||
not making these up, you know) in the bottom two bits of this to the CPL and if
|
||||
it is higher it changes SS and ESP to the recently popped values. This is really
|
||||
useful for software task switching.
|
||||
|
||||
So, you could easily get into a higher privilege level by intercepting
|
||||
a handled interrupt and changing the value of CS on the stack. If you set the
|
||||
bottom two bits to 0x3, you will soon be in User Mode.
|
||||
|
||||
An other (better in my opinion) option is to create a fake interrupt-pushed
|
||||
stack and push that onto the stack before running `IRET` .
|
||||
|
||||
// C code
|
||||
struct
|
||||
{
|
||||
uint32_t esp;
|
||||
uint32_t ss;
|
||||
uint32_t eflags;
|
||||
uint32_t eip;
|
||||
uint32_t cs;
|
||||
} fake_stack;
|
||||
|
||||
fake_stack.esp = usermode_stack_top;
|
||||
fake_stack.ss = user_data_segment | 0x3;
|
||||
fake_stack.eflags = 0;
|
||||
fake_stack.eip = &usermode_function;
|
||||
fake_stack.cs = user_code_segment | 0x3;
|
||||
|
||||
set_all_segments(user_data_segment | 0x3);
|
||||
run_iret(&fake_stack);
|
||||
{: .prettyprint}
|
||||
|
||||
; Assembler code
|
||||
run_iret:
|
||||
add esp, 0x8
|
||||
iret
|
||||
{: .prettyprint .lang-nasm}
|
||||
|
||||
|
||||
###Going back to ring0
|
||||
|
||||
I was going to continue this blog post with talking about how to switch from
|
||||
a higher CPL to a lower, but it is growing way longer than I thought it would.
|
||||
Therefore I will cut it off here, and continue in a new post.
|
||||
|
||||
###Application
|
||||
The methods described in this post is used in Git commit
|
||||
[52a0c84739](https://github.com/thomasloven/os5/tree/52a0c84739e04f3d9dd7410cdf0b378118a946b4).
|
||||
|
97
pages/2012-08-02-Return-To-Kernel-Mode.md
Normal file
@ -0,0 +1,97 @@
|
||||
layout: post
|
||||
title: "Return To Kernel Mode"
|
||||
subtitle: "Seriously, what's up with TSS?"
|
||||
tags: [osdev]
|
||||
|
||||
###Processor privilege levels
|
||||
|
||||
In [my last post](/blog/2012/07/Privilege-Levels) I wrote about x86 privilege
|
||||
levels. Remember the CLP, the DPL, the RPL and the KPL? Ok, I made that last
|
||||
one up, but there actually is one more -PL thing to take into consideration.
|
||||
The IOPL, but that won't come into play yet.
|
||||
|
||||
Anyway, I showed that switching privilege level was kind of easy, if you do it
|
||||
from a lower value to a higher. I also said that if you try to load a lower
|
||||
level than your CPL into _CS_ you get a GPF.
|
||||
|
||||
###Going to a lower privilege level
|
||||
|
||||
So, the processor designers obviously didn't want us to go to a lower
|
||||
protection level, so why would we? Well, there are some things that we want the
|
||||
kernel to do that requires a higher protection level. For example we may want
|
||||
to change the value of _cr3_ during a task switch in order to load a new
|
||||
address space for the new process.
|
||||
|
||||
Luckily, there is a way to get back to ring 0 (kernel mode) and that's through
|
||||
interrupts. When an interrupt happens (or is called by a program - more on this
|
||||
at an other time), the processor loads a new value for CS from the relevant
|
||||
interrupt descriptor in the IDT and - in this case - allows the change in
|
||||
privilege level no matter which way it goes. If the privilege level is changed
|
||||
by loading the new CS, the processor looks into the currently loaded __TSS__...
|
||||
|
||||
###Task State Segment
|
||||
|
||||
The __Task State Segment__ ( __TSS__) is another remnant from the olden days of
|
||||
the x86 processor architecture.
|
||||
|
||||
I'll gladly admit that I don't understand them at all, but it has to do with
|
||||
hardware task switching. In short, I believe that you can load a number of TSS
|
||||
entries into the GDT and load one of them into a special register at once to
|
||||
perform a task switch. The processor would then save all registers into the old
|
||||
TSS and load them from the new one, all in one instruction. ... or something
|
||||
like that...
|
||||
|
||||
But, as I mentioned above, if the privilege level is lowered during a processor
|
||||
interrupt, the processor looks into the currently loaded TSS. From there it
|
||||
loads a new value for SS and ESP. In other words, the TSS determines the stack
|
||||
position as we get back into kernel mode.
|
||||
|
||||
###Loading a TSS
|
||||
|
||||
Loading a TSS is actually rather simple and it's kind of hard to go wrong. So
|
||||
hard in fact that most tutorials actually do. Here's how it's really done
|
||||
(source: [Intel
|
||||
Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/))
|
||||
|
||||
gdt[TSS_DESCRIPTOR].base = &tss;
|
||||
gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
|
||||
gdt[TSS_DESCRIPTOR].flags = 0;
|
||||
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED;
|
||||
{: .prettyprint}
|
||||
|
||||
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
|
||||
(the TSS descriptor can not be stored in an LDT). Finally, like all segment
|
||||
descriptors, the TSS descriptor points to a segment that resides between the
|
||||
address _base_ and _base + limit_. In other words, _limit_ should be the size
|
||||
of the TSS, not the end of it.
|
||||
|
||||
What's the point in that, you might ask. The TSS is of fixed length, isn't it?
|
||||
Not really; the TSS is sometimes followed by a bitmap which decides which I/O
|
||||
ports the processor is allowed to access. If such a bitmap exists, the TSS
|
||||
segment is increased to incorporate it.
|
||||
|
||||
###Why you should get this right
|
||||
|
||||
The thing is that if you don't have an I/O map, any value for the limit above
|
||||
0x67 will be accepted by the processor. However, the limit field is only 20
|
||||
bits long and is often truncated to this length by cutting off the upper 12
|
||||
bits in the code that sets the descriptor. Most of the time, this works, but
|
||||
say your TSS begins at address 0xFFFFF and is of minimum length. That means it
|
||||
ends at 0x100066 and if you truncate this to 20 bits and put it as limit you
|
||||
get 0x66. Boom! Protection fault! If you're lucky, it happens every time you
|
||||
boot your kernel. If you're not, you may have a really nasty bug to track down.
|
||||
|
||||
###Back to the TSS
|
||||
|
||||
When you have your TSS segment descriptor, you need to set up the TSS itself.
|
||||
Without hardware multitasking, there are only three values of TSS that are
|
||||
reqired. SS0, ESP0 and IOMAP.
|
||||
|
||||
SS0 and ESP0 were described in my previous post and IOMAP is the offset of the
|
||||
I/O map inside the TSS segment. This needs to be set even if you don't have an
|
||||
IO map, so I just set it to the length of the TSS structure.
|
||||
|
||||
###Application
|
||||
The methods described in this post is used in Git commit
|
||||
[52a0c84739](https://github.com/thomasloven/os5/tree/52a0c84739e04f3d9dd7410cdf0b378118a946b4).
|
352
pages/2012-08-08-Thread-Stacks.md
Normal file
@ -0,0 +1,352 @@
|
||||
layout: post
|
||||
title: "Thread Stacks"
|
||||
subtitle: "4 a.m. - know where your stack pointer is?"
|
||||
tags: [osdev]
|
||||
|
||||
Since the x86 architecture has relatively few processor registers, a
|
||||
programmer may need additional space to store temporary values. For most
|
||||
compilers and languages, this space is the stack. C, for example, (gcc
|
||||
and clang at least) uses the stack to store local variables, function
|
||||
arguments and return addresses. In other words, the stack comes in use
|
||||
every time there is a function call.
|
||||
|
||||
The common way a function call is handled by a c compiler is this:
|
||||
- Push each argument to the stack (in reverse order)
|
||||
- Execute the `CALL` instruction (which pushes the address of the next
|
||||
instruction to the stack and jumps to the callee)
|
||||
|
||||
The callee does the following:
|
||||
- Push the base pointer to the stack
|
||||
- Sets the base pointer to the current stack pointer
|
||||
- Subtracts the stack pointer to reserve place for local variables.
|
||||
- Do its thing
|
||||
- Increase the stack pointer to free the space used by local variables.
|
||||
- Pop the base pointer from stack.
|
||||
- Execute the `RET` instruction (which puts the return value in EAX and
|
||||
jumps to the position at the top of the stack.
|
||||
|
||||
While the callee is doing its thing it now has access to all the pushed
|
||||
arguments at addresses (ebp + 8) and forwards and all local variables
|
||||
at addresses up to ebp. The return address is reachable at (ebp + 4) if
|
||||
you'd ever want that.
|
||||
|
||||
This convention makes it really easy to have functions which takes an
|
||||
undefined number of arguments, like `printf` does.
|
||||
|
||||
###Stacks in context switching
|
||||
It also makes for really simple _context switching_.
|
||||
Since the return address is stored on the stack, if you were to switch
|
||||
stacks inside a function, when you return, you'll be somewhere else.
|
||||
This is a common way of making usermode threads. Ponder the following:
|
||||
|
||||
void switch_thread()
|
||||
{
|
||||
push_all_registers();
|
||||
switch_stack_pointer();
|
||||
pop_all_registers();
|
||||
return;
|
||||
}
|
||||
|
||||
void a()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
|
||||
void b()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something_else();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
|
||||
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__
|
||||
runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
|
||||
running. The top of the stacks looks like:
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
+----------ESP----------+ |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
where `RA` means Return Address and `ESP` is where the stack pointer is
|
||||
currently pointing.
|
||||
As execution of __A__ continues, the processor will `do_something()` and
|
||||
then call `switch_thread()`...
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
+----------ESP----------+ |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
`switch_thread()` pushes all registers to the stack and calls
|
||||
`switch_stack_pointer()`
|
||||
|
||||
+----------ESP----------+ +-----------------------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
`switch_stack_pointer()` performs some scheduling to find out which
|
||||
thread is to run next, and then switches the stack pointer over to the
|
||||
top of __B__'s stack.
|
||||
|
||||
+-----------------------+ +----------ESP----------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
The processor keeps on executing code, and `switch_stack_pointer()` soon
|
||||
returns
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA| +----------ESP----------+
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
`switch_thread()` pops all registers and returns...
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
|switch_thread RA | +----------ESP----------+
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
... and we're now in `b()` with all registers of __B__ loaded.
|
||||
|
||||
###Stacks in the kernel
|
||||
When an interrupt or exception happens in user mode, [a new stack is
|
||||
loaded from the tss](/blog/2012/08/Return-To-Kernel-Mode/) and
|
||||
(usually) all registers are pushed onto it before the kernel starts the
|
||||
__Interrupt Service Routine__.
|
||||
|
||||
Wait... _all registers are pushed onto it_? I like the sound of that.
|
||||
That's, like, half the work of changing threads, right? Right!
|
||||
|
||||
If you've been following a kernel development tutorial (like [James
|
||||
Molloys](http://www.jamesmolloy.co.uk/tutorial_html/) or [Brandon
|
||||
Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
|
||||
have something like this to handle interrupts:
|
||||
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
call int_handler
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
{: .lang-nasm}
|
||||
|
||||
void int_handler(registers_t r)
|
||||
{
|
||||
do_stuff();
|
||||
}
|
||||
|
||||
In fact, if you've been following one of those tutorials, you probably
|
||||
have the above code twice, for some reason...
|
||||
|
||||
Anyway. This would take care of both pushing and poping all registers,
|
||||
and with only a small modification, it becomes very easy to switch the
|
||||
stacks too...
|
||||
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
push esp ;Pass stack pointer to int_handler
|
||||
call int_handler
|
||||
mov esp, eax ;int_handler returns a new stack pointer
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
{: .lang-nasm }
|
||||
|
||||
registers_t *int_handler(registers_t *r)
|
||||
{
|
||||
do_stuff();
|
||||
r = get_next_thread(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
This gives a pointer to the threads registers as input to the ISR and
|
||||
expect a pointer to some registers in return. They may or may not be the
|
||||
same.
|
||||
|
||||
###Keeping track of the stacks
|
||||
The saved registers are a large part of what defines each thread, but
|
||||
there are actually a few things more that are needed.
|
||||
|
||||
First of all, the kernel may want some extra information associated
|
||||
with each thread, such as scheduling information and a list of signal
|
||||
handlers.
|
||||
|
||||
Sometimes a thread in user mode will need help from the kernel which
|
||||
it cannot offer immediately. The thread may for example issue a read
|
||||
request to a file that's on a drive which has some spin-up time before
|
||||
it can be read. The kernel may then switch to another thread while the
|
||||
disk spins up. Therefore it's a good idea to have a separate kernel
|
||||
stack space for each thread.
|
||||
|
||||
With some thought, those three things can be easily combined into a
|
||||
single data structure. So let's think about it for a while.
|
||||
|
||||
While the thread is running we want some information stored somewhere in
|
||||
kernel space about it.
|
||||
|
||||
+-----------------------+
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
|
||||
Then, when an interrupt or syscall happens, a new stack is loaded
|
||||
and some stuff is pushed onto it. If we want this near our thread
|
||||
information it will have to go right before it, since the stack grows
|
||||
backwards.
|
||||
|
||||
+-----------------------+
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
|
||||
Finally, we want the kernel mode stack. Well... the stack pointer is
|
||||
right at the start of the registers now, so why not just continue the
|
||||
stack from there?
|
||||
|
||||
+-----------------------+
|
||||
| ... |
|
||||
|kernel mode stack |
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
|
||||
###Setting this up
|
||||
To set this up, the thread information structure has to be set up
|
||||
something like:
|
||||
|
||||
struct thread_info_struct
|
||||
{
|
||||
uint8_t stack_space[KERNEL_STACK_SIZE];
|
||||
registers_t r;
|
||||
struct thread_data_struct thread_data;
|
||||
} my_thread_info;
|
||||
|
||||
When the thread is running in user mode, the TSS should be set up in
|
||||
such a way that the stack pointer loaded at an interrupt points to the
|
||||
end of the registers, i.e. the beginning of the thread data.
|
||||
|
||||
TSS.esp0 = &my_thread_info.thread_data;
|
||||
|
||||
And that's really all there is to it. Unbelievable, really, how many
|
||||
years it took for me to figure this out.
|
||||
|
||||
In the process, I've found inspiration in [Rhombus by Nick
|
||||
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
|
||||
[linux](http://www.linux.org).
|
||||
|
||||
###Some considerations
|
||||
In order to do the actual switching of threads, I implemented a special
|
||||
syscall which can be called only from kernel mode.
|
||||
|
||||
Let's say a user mode program calls `yield()`. This performs a syscall
|
||||
in the form of an interrupt instruction `INT 0x80` and thus we jump into
|
||||
the kernel.
|
||||
|
||||
The kernel performs some housekeeping and selects a new thread to run.
|
||||
It then performs the special switching interrupt `INT 0x82`.
|
||||
|
||||
Since we're already in kernel mode, no new stack is loaded but the
|
||||
registers are pushed onto the old one. The top of the kernel stack will
|
||||
then contain a `registers_t` structure and a pointer to it is saved in
|
||||
a `kernel_stack` variable in the `thread_data` portion of the thread
|
||||
information structure.
|
||||
|
||||
Next, the thread information structure of the new thread is read and
|
||||
the `kernel_stack` pointer from it is returned to the `int_stub` as
|
||||
above. The `IRET` instruction brings us back to wherever we were before
|
||||
(probably in kernel mode, but could as well be user mode). If the new
|
||||
thread was swapped out while in kernel mode, it will carry on from
|
||||
wherever it was and eventually return to user mode.
|
||||
|
||||
This way of handling kernel stacks also makes for really clean nesting
|
||||
of interrupts.
|
||||
|
||||
###Usage
|
||||
This method has been implemented in git commit
|
||||
[756852fc66](https://github.com/thomasloven/os5/tree/756852fc66b80b1e605
|
||||
8d74b8dc334ad841ec5ea)
|
||||
|
||||
###A warning
|
||||
I recently learned - the hard way - that the [clang
|
||||
compiler](http://clang.llvm.org) does not use this calling convention
|
||||
for functions which do not in turn call other functions. I.e
|
||||
|
||||
int double_integer(int a)
|
||||
{
|
||||
return 2*a;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
double_integer(5);
|
||||
}
|
||||
|
||||
If this code is compiled with clang `double_integer` will (in some
|
||||
cases) not push `ebp` to stack.
|
||||
|
||||
This severely hinders many debuggers and should be considered a bug in
|
||||
my oppinion.
|
53
pages/2013-01-16-10-Kinds-Of-People.md
Normal file
@ -0,0 +1,53 @@
|
||||
layout: post
|
||||
title: "10 Kinds of People"
|
||||
subtitle: "... and some 7 billion more kinds."
|
||||
|
||||
We've all read the joke.
|
||||
|
||||
> There are 10 types of people in the world... Those who understand
|
||||
> binary and those who don't."
|
||||
|
||||
Or the one people who pride themselves to be a bit more nerdy than the
|
||||
nerdy ones prefer:
|
||||
|
||||
> There are 10 types of people in the world ... Those who understand
|
||||
> hexadecimal and 15 others."
|
||||
|
||||
There's even a quote about this on [bash.org](http://bash.org/?top).
|
||||
|
||||
> kow: "There are 10 types of people in the world... those who
|
||||
> understand binary and those who don't."
|
||||
|
||||
> SpaceRain: That's only 2 types of people, kow.
|
||||
|
||||
> SpaceRain: STUPID
|
||||
|
||||
I'll tell you; this SpaceRain dude has a point.
|
||||
|
||||
In fact, I'd say:
|
||||
|
||||
### There are 10 types of people in the world... Those who doesn't know how to communicate a number base properly, and nine types who do.
|
||||
|
||||
As [xkcd](http://xkcd.com/169/) put it:
|
||||
|
||||
> Communicating badly and then acting smug when you're misunderstood is
|
||||
> not cleverness.
|
||||
|
||||
Of course, "In base 2 there are 10 types of people in the world..."
|
||||
isn't very funny, but - in all fairness - the original joke isn't very
|
||||
funny anymore either. However, "There are 010 types of people in the
|
||||
world... Those who can use google to look up octal numbering conventions
|
||||
and 7 others", is a bit funny to my fancy, even if it's a very strange
|
||||
way of classifying people.
|
||||
|
||||
Maybe we all should just move on from this joke or at least bring it to
|
||||
the next level.
|
||||
|
||||
I'll end this with a version that is actually funny and which I regret
|
||||
to say I will probably never find the original source of.
|
||||
|
||||
> "There are 2 types of people in the world... Those who can extrapolate
|
||||
> from incomplete data"
|
||||
|
||||
Also, I recently added commenting to the blog posts. Please, fill me in
|
||||
on your versions.
|
163
pages/2013-01-20-Z80-Information.md
Normal file
@ -0,0 +1,163 @@
|
||||
layout: post
|
||||
title: "Z80 Information"
|
||||
subtitle: "A humble beginning"
|
||||
tags: [electronics]
|
||||
|
||||
Six or seven years ago, I bought a box full of fun stuff on eBay. It
|
||||
turned out to include a few Z80 processors. I had just finished my army
|
||||
duty and still remembered the math and physics lectures from college
|
||||
(equivalent) where most of the time had been wasted playing Super Mario
|
||||
on our Texas Instruments TI83+ graphing calculators who run on the very
|
||||
same processor. So I was well aware on what wonders it's capable of.
|
||||
Unfortunately, I didn't even have time to start playing with my new
|
||||
goodies before I went to university and my life turned into a mess.
|
||||
|
||||
The time finally came about a year and a half ago. I was having the
|
||||
first vacation of my life and actually had time to spare.
|
||||
|
||||
At this point I had no kind of plan. So I started out by trying to find
|
||||
out if at least one of the Z80s actually worked.
|
||||
|
||||
History of the Z80
|
||||
------------------
|
||||
According to [Wikipedia](http://en.wikipedia.org/wiki/Zilog_Z80),
|
||||
the Z80 processor was launched in 1976 by Zilog. The founder of Zilog
|
||||
had previously worked for Intel on their 8080 microprocessor. The Z80
|
||||
is therefore designed to run any code which runs on the former.
|
||||
|
||||
Further, the Z80 features an extended instruction set compared to the
|
||||
8080. The extra instructions include bit and block operations.
|
||||
The Z80 also has two sets of registers which can be swapped through a
|
||||
special register, indexed addressing and vectorized interrupts.
|
||||
|
||||
I have no experience working with the 8080 and this far only a little
|
||||
bit of actually programming a Z80. I've already, however, begun to
|
||||
appreciate some of those features.
|
||||
|
||||
What I appreciate even more - at least this far - is the following
|
||||
hardware features of the Z80 compared to the 8080.
|
||||
- A single 5V power supply - as opposed to +5V, -5V and +12V for the 8080
|
||||
- A single clock signal input - as opposed to two for the 8080
|
||||
|
||||
What this means
|
||||
---------------
|
||||
What those hardware features mean, is that getting a Z80 to run is
|
||||
actually really simple. All we need in ways of support circuits are a
|
||||
single square wave clock signal and a reliable reset circuit. Oh, and a
|
||||
power supply of course.
|
||||
|
||||
A step back
|
||||
-----------
|
||||
But we shouldn't get ahead of ourselves here. Let's instead take a step
|
||||
back and look at the Z80.
|
||||
|
||||
{: .right .noborder}
|
||||
This is the pinout of a standard Z80 in a 40-pin DIP package. I have
|
||||
colorized the pins to show one way of grouping their functions.
|
||||
|
||||
A line above the pin name or a `/` before it (such as `/INT`) indicates
|
||||
that the pin is Active Low, i.e. 0V means yes and 5V means no in layman
|
||||
terms.
|
||||
|
||||
###Green
|
||||
The green pins are the address bus. A0 to A15 are used to indicate an
|
||||
address in memory during a memory read or write operation. A0 to A7 are
|
||||
also used to select I/O device during a port read or write operation.
|
||||
The address bus is output only.
|
||||
|
||||
###Red
|
||||
The red pins are the data bus. D0 to D7 are used to transfer or receive
|
||||
data during a memory or port read or write operation. The data bus can
|
||||
also be used to indicate which device fired an interrupt. The data bus
|
||||
can be both input and output.
|
||||
|
||||
###Blue
|
||||
The blue pins are what keeps the Z80 going. The 5V DC power supply,
|
||||
a ground connection and a clock signal. We'll look more on the clock
|
||||
signal later.
|
||||
|
||||
###Orange
|
||||
The orange pins are used to control the Z80.
|
||||
|
||||
- `/INT` indicates that a hardware interrupt occurred and makes the Z80
|
||||
act on this.
|
||||
- `/NMI` is a Non Maskable Interrupt and has higher priority than the `INT`
|
||||
- `/WAIT` can be used by memory or I/O devices to make the Z80 wait
|
||||
during a read or write operation while the device prepares to fill the
|
||||
request.
|
||||
- `/BUSRQ` is used by external devices to request control over the data,
|
||||
address and system control buses. When the Z80 is ready to hand over
|
||||
control, this is signaled to the requesting device via the `BUSACK`
|
||||
pin.
|
||||
- `/RESET` is used to reset the Z80 into a well defined state.
|
||||
|
||||
All orange pins are input only.
|
||||
|
||||
###Yellow
|
||||
The yellow pins are used to control peripherals and other parts of the
|
||||
computer system.
|
||||
|
||||
- `/HALT` indicates that the Z80 is in a halted state, i.e. it is waiting
|
||||
for an interrupt to happen.
|
||||
- `/MREQ` indicates that the Z80 wishes to access memory.
|
||||
- `/IORQ` indicates that the Z80 wishes to access an I/O port.
|
||||
- `/RD` goes low during a memory or I/O read operation.
|
||||
- `/WR` goes low during a memory or I/O write operation.
|
||||
- `/BUSACK` indicates that the Z80 has let an other device take control
|
||||
of the buses.
|
||||
- `/M1` indicates that the Z80 is fetching the next instruction from
|
||||
memory. This pin turned out to be incredibly useful for debugging.
|
||||
- `/RFSH` back in the days memory circuits couldn't keep their contents
|
||||
indefinitely, even with power on, but had to be given a refresh pulse
|
||||
every now and then. This usually required some extra circuitry which
|
||||
kept track of which memory addresses needed refreshing when. The Z80,
|
||||
however, outputs a refresh signal from time to time and also gives an
|
||||
address to refresh at A0 to A6(!).
|
||||
|
||||
All yellow pins are output only.
|
||||
|
||||
Fetch Decode Execute
|
||||
--------------------
|
||||
A CPU operates in what's called instruction cycles. The instruction
|
||||
cycles can be broken down into three sub-cycles, namely the Fetch cycle,
|
||||
the Decode cycle and the Execute cycle.
|
||||
|
||||
During the Fetch cycle, the processor reads an instruction from memory.
|
||||
It keeps the address of the next instruction to read stored in a special
|
||||
register, the Program Counter _PC_. The Z80 also outputs the refresh
|
||||
signal during the end of the Fetch cycle. After reading an instruction
|
||||
_PC_ in incremented by one.
|
||||
|
||||
During the Decode cycle, the processor decodes the read instruction.
|
||||
If more data needs to be read from memory (e.g. an address in a jump
|
||||
instruction) it is read now (_PC_ is incremented accordingly).
|
||||
|
||||
During the execute cycle, the processor does what the instruction told
|
||||
it to. It might for example - in the case of a jump instruction - change
|
||||
the contents of the _PC_ register, or add two registers together.
|
||||
|
||||
The cycle then repeats indefinitely or until the processor is halted.
|
||||
|
||||
The simplest instruction
|
||||
------------------------
|
||||
So... the goal of this post was to make a Z80 tester, right? Actually,
|
||||
that will probably have to wait for the next post, because this one is
|
||||
getting really long now. However, it makes sense to look at one last
|
||||
piece of information right now. To make sure a Z80 is working, we'd
|
||||
want it to go through a number of instruction cycles. As a first step,
|
||||
though, we don't actually need it to do anything, but just move on to
|
||||
the next instruction.
|
||||
|
||||
Luckily, there's an instruction for this.
|
||||
|
||||
From the [Z80 CPU Users Manual](http://www.z80.info/zip/z80cpu_um.pdf) by Zilog:
|
||||
|
||||
> NOP
|
||||
>
|
||||
> Description: The CPU performs no operation during this machine cycle.
|
||||
|
||||
Sounds just like what we need! To make it even more perfect, the `nop`
|
||||
instruction is indicated by the byte `0x00` or all zeroes.
|
||||
|
||||
I'll let you think about the implications of this until next time.
|
||||
|
172
pages/2013-01-21-Z80-Tester.md
Normal file
@ -0,0 +1,172 @@
|
||||
layout: post
|
||||
title: "Z80 Tester"
|
||||
subtitle: "Bouncing buttons"
|
||||
tags: [electronics]
|
||||
|
||||
In the [last post](/2013/01/Z80-Information/) I said we only needed two
|
||||
pieces of support circuits to get a Z80 running, namely a clock and a
|
||||
reset circuit.
|
||||
|
||||
Let's take a look at the most advanced one first; the clock signal
|
||||
generator.
|
||||
|
||||
{: .right}
|
||||
|
||||
Clock signal
|
||||
------------
|
||||
|
||||
The Z80 needs a 5V square clock signal.
|
||||
|
||||
For testing purposes it's ok to generate the clock signal by hand. The
|
||||
easiest way to do this requires only a switch and a resistor configured
|
||||
like the figure on the right.
|
||||
|
||||
Let me just stop you right now and tell you that this won't work.
|
||||
|
||||
Why? Well, take a look at the output in an oscilloscope:
|
||||
|
||||

|
||||
|
||||
Doesn't look so bad, does it? Well, let's take a closer look at that
|
||||
falling edge to the left. Here it is, zoomed in about 250 times:
|
||||
|
||||

|
||||
|
||||
Now that's not pretty. If you used this clock circuit, for every time
|
||||
you press the button, the Z80 will receive a dozen pulses or two. That
|
||||
makes for some very unrepeatable behavior, and repeatability is what
|
||||
computers are all about.
|
||||
|
||||
{: .right}
|
||||
|
||||
A better clock signal
|
||||
---------------------
|
||||
Here's a good clock generator. It requires two switches which are pushed
|
||||
alternately, two pull-up resistors and two NAND gates (e.g. 74HC00).
|
||||
|
||||
This is what's called an S-R-latch (S-R is short for Set-Reset).
|
||||
|
||||
Let's say the clock output is currently HIGH (+5V) and that no switches
|
||||
are pressed. The upper NAND gate then has two HIGH inputs, which means
|
||||
its output is LOW (0V). The lower NAND has one LOW input and one HIGH,
|
||||
which means its output is HIGH. By pushing the top button, one of the
|
||||
inputs to the top NAND is grounded so that its input is (LOW, HIGH)
|
||||
which means it outputs HIGH. This means the lower NAND gets the input
|
||||
(HIGH, HIGH) which makes its output LOW and when the button is released,
|
||||
the top NAND will have the input (HIGH, LOW) and thus still output HIGH.
|
||||
|
||||
It's a bit simpler to understand if you just think about it than if you
|
||||
try to read my messy explanation, but you must assume that the clock
|
||||
signal is either HIGH or LOW before you start thinking.
|
||||
|
||||
{: .left}
|
||||
Anyway, I built this circuit on a bread board and measured it the same
|
||||
way as the last one.
|
||||
|
||||

|
||||
|
||||
Nice and clean. We've got our clock source.
|
||||
|
||||
Reset switch
|
||||
------------
|
||||
When it comes to reset the Z80 is not as picky as with the clock signal.
|
||||
In fact, we can use the first attempt at a clock signal for reset.
|
||||
|
||||
The internal reset circuitry of the Z80 depends on the clock signal,
|
||||
so as long as the clock doesn't move, whatever happens with the reset
|
||||
signal doesn't matter.
|
||||
|
||||
This fact is important to rember for other reasons too; to perform a
|
||||
reset, the reset pin must be pulled low for a few clock cycles.
|
||||
|
||||
In other words: To perform a reset, push the reset button, toggle the
|
||||
clock a handful of times (minimum three), release the reset button.
|
||||
|
||||
That took me a while to figure out and is why you should always read
|
||||
your datasheets carefully, kids.
|
||||
|
||||
A similar note of caution: Don't leave the clock signal in its LOW state
|
||||
for any extended periods of time or the Z80 will forget its state. To be
|
||||
sure, always make the clock HIGH right after making it LOW. You could
|
||||
add an LED to show its current state.
|
||||
|
||||
Let's wire it up
|
||||
|
||||
Z80 tester
|
||||
----------
|
||||

|
||||
|
||||
Here's a Z80 tester circuit wired up on my breadboard.
|
||||
|
||||
The connections are as follows:
|
||||
|
||||
- `D0`-`D7` are pulled low through resistors of ~1kΩ.
|
||||
- `/INT`, `/NMI`, `/WAIT` and `/BUSRQ` are connected to +5V.
|
||||
- `+5V` and `GND` are connected as labeled.
|
||||
- `/RESET` is connected to a pull up resistor and a button as described
|
||||
above.
|
||||
- `CLK` is connected to an Arduino nano which generates a 500 Hz square
|
||||
signal (I felt a bit lazy).
|
||||
- `A0`-`A7` are connected to the logic probes of my oscilloscope.
|
||||
|
||||
But before I power this up, let's think about what we're expecting to see.
|
||||
|
||||
As we release the reset button (after keeping it down for three clock
|
||||
cycles or more) the Z80 starts to read instructions at address `0x0000`.
|
||||
|
||||
Since all data pins are permanently tied low it will read the value
|
||||
`0x00` which corresponds to a `NOP` instruction. This means it skips
|
||||
ahead to address `0x0001` to read its next instruction and so on.
|
||||
|
||||
In other words, we expect the 8 lowest address lines to count upwards
|
||||
binarily from 0 to 255. Since there are 8 address lines we're not
|
||||
monitoring, it should make this count 255 times before getting back to
|
||||
address `0x0000` again. But we won't be able to see the difference
|
||||
between the counts.
|
||||
|
||||
Ok, let's take a look.
|
||||
|
||||

|
||||
Just like that. This Z80 seems to be working fine.
|
||||
|
||||
All 255 addresses didn't quite fit in this picture, but you can see
|
||||
address `0xXX00` to the left and `0xXX7B` to the right.
|
||||
|
||||
Scrolling forwards a bit in time we find something interesting.
|
||||

|
||||
|
||||
See that thing on `A7`? It's not stable, but is pulsing. The same
|
||||
behavior can be seen on `A8`-`A15`. But why?
|
||||
|
||||
Dynamic memory refresh, that's why.
|
||||
|
||||
As I described in the last post; right after fetching an instruction,
|
||||
the Z80 sends a refresh pulse to the memory chips. During this pulse, it
|
||||
also sends an address on the 7 lowest address lines, i.e. `A0`-`A6`. The
|
||||
other address lines are pulled low.
|
||||
|
||||
Let's take a closer look at the signals used.
|
||||
|
||||

|
||||
|
||||
Here I also connected to the oscilloscope
|
||||
- `/M1`
|
||||
- `/MREQ`
|
||||
- `/RD`
|
||||
- `CLK`
|
||||
|
||||
We can clearly see the instruction fetch cycle. `/M1` goes low, followed
|
||||
by `/MREQ` and then `/RD`. Then all three goes high while the processor
|
||||
is decoding the instruction.
|
||||
|
||||
During the decode cycle, we see `/MREQ` go low again. This is the memory
|
||||
refreshing. I didn't connect the `/RFSH` line to the oscilloscope but,
|
||||
trust me, it goes low at about the same time.
|
||||
|
||||
Next is the execute cycle, which for the `NOP` instruction is nothing at
|
||||
all, so the processor jumps right to the next instruction fetch cycle.
|
||||
|
||||
|
||||
Ok... so the Z80 is indeed working as expected.
|
||||
|
||||
Next time we'll look at a control panel.
|
37
pages/2013-02-06-A-Step-Back.md
Normal file
@ -0,0 +1,37 @@
|
||||
layout: post
|
||||
title: "A Step Back"
|
||||
subtitle: "Exactly why I keep this blog"
|
||||
tags: [osdev]
|
||||
|
||||
I've previously described my normal mode of enjoying the hobby of
|
||||
operating system development, i.e. restarting from the ground every time
|
||||
I get some time, and then forgetting everything once school starts.
|
||||
|
||||
That's exactly why I started writing down what I'm doing in this blog.
|
||||
|
||||
The thing is, I didn't really keep to this at the end of summer, and ran
|
||||
ahead a bit with the programming, thinking I should catch up with the
|
||||
blogging at a later time. Obviously, I didn't do this...
|
||||
|
||||
When I took a look at the state of my code a week or so ago, I
|
||||
noticed I hadn't even checked in my changes since August. So that's
|
||||
what I did, and then I branched off the last commit I blogged about
|
||||
([756852fc66](https://github.com/thomasloven/os5/tree/756852fc66b80b1e60
|
||||
58d74b8dc334ad841ec5ea)) and made that the new master branch.
|
||||
|
||||
For future reference (I'll probably cheat again someday) there were the commands for this:
|
||||
|
||||
git commit -m 'Bad excuse for not checking in before'
|
||||
git checkout -b new_master OLD_COMMIT_SHA
|
||||
git merge --strategy=ours master
|
||||
git checkout master
|
||||
git merge new_master
|
||||
|
||||
The resulting commit is found at
|
||||
[f74ec287db](https://github.com/thomasloven/os5/tree/f74ec287db488a7bda5
|
||||
4ad75f979ca6b5664feef).
|
||||
If you take a look at my [commit
|
||||
list](https://github.com/thomasloven/os5/commits/), though, you'll
|
||||
notice that I cheated again... I told you I would!
|
||||
|
||||
Anyway. I'll get right on describing what I've been doing...
|
177
pages/2013-02-06-New-Environment.md
Normal file
@ -0,0 +1,177 @@
|
||||
layout: post
|
||||
title: "New Environment"
|
||||
subtitle: "A bit more and less modern tools"
|
||||
tags: [osdev]
|
||||
|
||||
Two summers ago, I didn't always have access to my build computer, so I
|
||||
came up with a convoluted development setup which could be run over an
|
||||
ssh connection.
|
||||
|
||||
I would edit files locally using some graphical text editor. Then I
|
||||
would save my files and wait for them to propagate to the build computer
|
||||
through Dropbox.
|
||||
Next I would run the build commands through ssh and then test the
|
||||
results in bochs terminal mode.
|
||||
|
||||
Last summer I started using vim to edit the source directly on the build
|
||||
computer, which meant I didn't have to wait for Dropbox.
|
||||
Around the same time I started to find some awesome tools which have
|
||||
been around for ages.
|
||||
|
||||
Those are, for example [tmux](http://tmux.sourceforge.net/) which is a
|
||||
terminal multiplexer which runs circles around gnu screen in terms of
|
||||
usability and features.
|
||||
|
||||
LLVM
|
||||
----
|
||||
|
||||
I also started looking at the [llvm](http://llvm.org) compiler suite.
|
||||
The main reason I've been using a cross compiler is that I'm building
|
||||
under OSX. OSX has its own executable format (Mach-O something or other),
|
||||
but I'd like to use gnu elf, because it's well documented and there's
|
||||
lots of example code about. The gcc bundled with OSX only compiles for
|
||||
OSX, obviously, but OSX recently started shipping with llvm and clang
|
||||
instead.
|
||||
|
||||
Clang can apparently be used as a cross compiler whatever way it was
|
||||
built, though the available documentation is a bit unclear on how
|
||||
exactly to do this. Come to think of it, the available documentation is
|
||||
a lot unclear on just about everything. Funny for a compiler which
|
||||
boasts expressive error messages as a feature...
|
||||
|
||||
Anyway with clang version 3.1 you can compile i386-elf object files through
|
||||
|
||||
> clang -ccc-host-triple i386-pc-linux -c source.c -o object.o
|
||||
{: .prettyprint .lang-sh}
|
||||
|
||||
`-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
|
||||
standard does not work...
|
||||
|
||||
Since clang version 3.2 `-ccc-host-triple` has apparently been replaced
|
||||
by `-target`. This change is, of course, not mentioned in the release
|
||||
notes.
|
||||
|
||||
In short, the llvm project - though I appreciate the thought - is an
|
||||
excellent example of why you should document your code, kids.
|
||||
|
||||
This being said. If you know of some secret stash of llvm or clang
|
||||
documentation, please - PLEASE - let me know!
|
||||
|
||||
I still use clang for compiling my kernel, though. Mostly because of the
|
||||
aforementioned expressive error messages. They really are a nice change
|
||||
to gcc and in pretty colors too.
|
||||
|
||||
If you run OSX clang 3.1 is installed with the
|
||||
current version of Xcode. Version 3.2 is installed by
|
||||
[Homebrew](http://mxcl.github.com/homebrew/).
|
||||
|
||||
> homebrew install llvm
|
||||
{: .prettyprint .lang-sh}
|
||||
|
||||
Binutils
|
||||
--------
|
||||
|
||||
The linker supplied with llvm doesn't read linker scripts - and I didn't
|
||||
even get it to link my kernel objects together at all anyway - so I
|
||||
still use gnu binutils cross compiled for i386-elf.
|
||||
|
||||
However, I don't use the compiling process described in an earlier post.
|
||||
Instead, again, I use Homebrew.
|
||||
|
||||
First of all, to get a working cross target binutils, the brew formula
|
||||
will have to be changed a bit
|
||||
|
||||
> brew edit binutils
|
||||
|
||||
Change the last configure flag (` --enable-targets=x86_64-elf,arm-none-eabi,m32r`)
|
||||
to `--target=i386-elf`. I also changed the `--program-prefix` to `i386-elf-`. Save the file and run
|
||||
|
||||
> brew install binutils
|
||||
|
||||
and you're good to go.
|
||||
|
||||
As a bonus, binutils also contains usefull tools like objdump and readelf.
|
||||
|
||||
Qemu
|
||||
----
|
||||
|
||||
Up until a few days ago, after compiling my kernel, I mounted an ext2
|
||||
image using mac-fuse, copied the kernel binary into it, unmounted the
|
||||
image and tested it using bochs.
|
||||
Bochs has a really nice text based interface which works well as long as
|
||||
the OS is only text based anyway, also a great debugger. Unfortunately,
|
||||
you can't use both at once, but most of the time you don't need an
|
||||
actual debugger to find the problems anyway.
|
||||
|
||||
The whole process can be streamlined through some shell scripting and I
|
||||
had the overhead reduced to almost nothing, I thought...
|
||||
|
||||
Then I decided to take a look at [qemu](http://wiki.qemu.org/Main_Page)
|
||||
again. I used it once before, but stopped due to the lack of a debugger
|
||||
and text mode if I recall correctly. I knew back then that it could be
|
||||
debugged with [gdb](http://www.gnu.org/software/gdb/), but I was on a
|
||||
windows machine in those days, and... well... don't really want to talk
|
||||
about it...
|
||||
|
||||
Anyway, I started having some problems with mac-fuse, so I thought
|
||||
I should take a look at qemu again, since it has built in emulation
|
||||
of a boot loader and can run a lone kernel binary passed to it as an
|
||||
argument. So I started to look it up and it turned out to actually have
|
||||
a text mode (curses mode).
|
||||
|
||||
So I went out on a whim and tried
|
||||
|
||||
> brew info qemu
|
||||
|
||||
By now you should know pretty much what I think of Homebrew, so the
|
||||
results of that command pretty much sealed the deal.
|
||||
|
||||
Now I run my kernel in qemu through
|
||||
|
||||
> qemu-system-i386 -kernel kernel/kernel -curses
|
||||
|
||||
Qemu also turned out to have a monitor mode which contains some of the
|
||||
functions I used most often in the bochs debugger, such as printing the
|
||||
memory map. Further, this could be accessed using telnet from a
|
||||
different tmux pane.
|
||||
|
||||
#!/bin/bash
|
||||
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
|
||||
tmux select-pane -L
|
||||
telnet localhost 4444
|
||||
{: .prettyprint}
|
||||
|
||||
Finally, I also installed an i386-elf targeted version of gdb - using Homebrew, obviously, with the same trick as for binutils. Gdb is found in a different tap of homebrew, so that will have to be installed first
|
||||
|
||||
> brew tap homebrew/dupes
|
||||
> brew edit gdb
|
||||
|
||||
Add the flag `--target=i386-elf` to the configure flags, save and
|
||||
|
||||
> brew install gdb
|
||||
|
||||
This will link to `ì386-elf-gdb` and can be run in yet another tmux window.
|
||||
|
||||
#!/bin/bash
|
||||
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
|
||||
tmux select-pane -L
|
||||
tmux slit-window -v 'i386-elf-gdb'
|
||||
tmux select-pane -U
|
||||
telnet localhost 4444
|
||||
|
||||
Upon start, gdb will look for a file called `.gdbinit` which in my case contains
|
||||
|
||||
file kernel/kernel
|
||||
target remote localhost:1234
|
||||
|
||||
Results
|
||||
-------
|
||||
|
||||
[](/media/img/osdev_build_env_full.png)
|
||||
|
||||
Now that's neat!
|
||||
|
||||
The results can also be seen in git commit [0699c20381](https://github.com/thomasloven/os5/tree/0699c203818ec1d018c93b0192fba48ccb6879d8).
|
||||
|
||||
|
127
pages/2013-02-09-More-Memory.md
Normal file
@ -0,0 +1,127 @@
|
||||
layout: post
|
||||
title: "More Memory"
|
||||
subtitle: "And processes"
|
||||
tags: [osdev]
|
||||
|
||||
The kernel has been multithreaded for quite a while, so now it's time to
|
||||
make it multiprocessing. Here's how I look at processes and threads in
|
||||
single-processor systems.
|
||||
|
||||
###Thread
|
||||
A thread or __thread of execution__ is the state of the processor at a
|
||||
certain point. That is, the thread is made up of
|
||||
|
||||
- processor registers
|
||||
- instruction pointer
|
||||
- stack pointer
|
||||
|
||||
By saving those and replacing with other previously saved values,
|
||||
threads can be switched in and out of execution. Each thread has its
|
||||
own, unique stack.
|
||||
|
||||
###Process
|
||||
A process is an isolated collection of threads. That is:
|
||||
|
||||
- A process has one or more threads.
|
||||
- The threads in a process share memory space
|
||||
- The threads in one process can not access the memory of the threads
|
||||
in another process
|
||||
|
||||
There are some exceptions to the third point, but that's another show.
|
||||
|
||||
Each process has an associated memory translation map which can be
|
||||
switched out to switch the active process. Generally, the current thread
|
||||
will have to be changed at the same time since its code and data will be
|
||||
in another memory space.
|
||||
|
||||
The kernel code and data resides in the upper part of memory, from
|
||||
`0xC0000000` and above and is common to all memory translation maps.
|
||||
That means that a kernel thread can be switched in without changing the
|
||||
process, and also that the process can be switched freely while a kernel
|
||||
thread is running.
|
||||
|
||||
###Synchronizing Memory Translation maps
|
||||
|
||||
The x86 memory management unit (MMU) governs how the processor accesses
|
||||
memory. When paging is enabled any call to any address during code
|
||||
execution is translated from a _virtual address_ to a _physical
|
||||
address_. I usually call the set of translation rules a _memory
|
||||
translation map_.
|
||||
|
||||
Each process has its own assigned memory translation map. Two processes'
|
||||
memory translation maps may in unlikely cases map the same physical
|
||||
memory pages to the same virtual addresses, but the maps are still
|
||||
considered unique.
|
||||
|
||||
The mapping of the kernel memory space is equal in all maps. This means
|
||||
that as long as the processor is executing code in the kernel space,
|
||||
the active memory translation map may be changed without worrying about
|
||||
corrupting data, since there's no difference between them. It does cause
|
||||
a problem though. If something changes in the kernel space, the same
|
||||
change must happen in all memory translation maps.
|
||||
|
||||
The way memory is translated makes the problem a little bit smaller.
|
||||
Since the x86 family uses two-tier paging and, again, kernel space is
|
||||
the same for all maps, we can use the same page tables for all maps.
|
||||
That means we only need to consider changes in the page directory which
|
||||
happens significantly less frequently (a naïve approximation: 1024 times
|
||||
less often).
|
||||
|
||||
Still, when a change is made to the page directory which corresponds to
|
||||
an address in kernel space, the change needs to be propagated to all
|
||||
page directories in the system. There are a few ways this can be done:
|
||||
|
||||
- Keep track of all memory translation maps and update them all at the
|
||||
same time when something happens to the kernel space.
|
||||
- When a new memory translation map is switched in, copy any changes
|
||||
from the previous one.
|
||||
- Keep a master memory translation map and update that when a change is
|
||||
made, then propagate the changes on request.
|
||||
|
||||
The first method sounds like a lot of work. Keeping track of the memory
|
||||
translation maps isn't very hard - they all belong to a process, and we
|
||||
need to keep track of all processes anyway. Updating them, however could
|
||||
take a lot of time. As I write this, I have 164 processes currently
|
||||
running on my computer. That's 164 memory translation maps that should
|
||||
be updated, some of which may be currently paged out and written to
|
||||
disk.
|
||||
|
||||
The second method sounds feasible. During the switch, we are probably
|
||||
doing things to both memory translation maps anyway, so why not add this
|
||||
step? It does add some overhead, but shouldn't be too bad.
|
||||
|
||||
The third method also sounds feasible. But how do we know when the
|
||||
changed part is requested? The answer is page faults. If a page fault
|
||||
occurs in kernel space and it turns out to be caused by a missing page
|
||||
directory entry, check the master page directory. If there is an entry
|
||||
there, add it to the current page directory and you're good to go.
|
||||
|
||||
Unfortunately, this means we can only add new page directory entries and
|
||||
never change or delete them, because that wouldn't cause a page miss the
|
||||
next time. Also, all the kernel page tables would need to be kept in
|
||||
memory at all times and never be paged out. The memory hit isn't too bad
|
||||
on a modern system, but it seems a bit inelegant now that I think about
|
||||
it.
|
||||
|
||||
###What to use?
|
||||
|
||||
This brings up one of the points to why I'm keeping this blog. It makes
|
||||
me think about stuff.
|
||||
|
||||
Before I did the reset described in [a previous
|
||||
post](/blog/2013/02/A-Step-Back/) I used the second method above. When I
|
||||
wrote the new code, I came up with the third method and thought I should
|
||||
try it out. Now I'm starting to have second thoughts, though...
|
||||
|
||||
I'll give it some more thought and we'll see how it turns out.
|
||||
|
||||
###Next step
|
||||
|
||||
Again, I'm starting to ramble and the post is getting long and
|
||||
embarrassingly unstructured so I'll cut it off here.
|
||||
What I'll save for the next post is how to keep track of the memory
|
||||
space for the processes.
|
||||
|
||||
###Usage
|
||||
The methods described in this post has been implemented in git commit
|
||||
[fa9e5929ce](https://github.com/thomasloven/os5/tree/fa9e5929ce6adaf62e6a85df284690b31163a4f9)
|
55
pages/2013-03-05-Two-Small-Projects.md
Normal file
@ -0,0 +1,55 @@
|
||||
layout: post
|
||||
title: "Two Small Projects"
|
||||
subtitle: "Audio and Arduino"
|
||||
|
||||
###Breadboard Arduino
|
||||
I didn't really like the arduino when I first heard about it. It
|
||||
was vastly overpowered for any task I saw it used for and with the
|
||||
interpreted coding it seemed little more than an expensive waste of
|
||||
processor cycles.
|
||||
|
||||
Then I went and bought one anyway, and I am happy to admit that I was
|
||||
wrong to think it useles. I remember opening the box, and ten minutes
|
||||
later I had used it to solve a problem which had been bothering me for a
|
||||
week. Now I'm thinking of getting a third one.
|
||||
|
||||
The one I use the most is an Arduino nano, which can be seen sitting on
|
||||
my breadboard in this photo from a previous post
|
||||

|
||||
|
||||
This placement worked well, but I kept thinking about the big open area
|
||||
just to the left of the Arduino...
|
||||
|
||||
So, one day I brought out the dremel and... perfect fit!
|
||||

|
||||
|
||||
To the sides are an IC holder I split in half and soldered on the back.
|
||||

|
||||
|
||||
It was a bit tricky to avoid melting the plastics, but I think it turned
|
||||
out allright.
|
||||
|
||||
Finally, heres a picture of it in use for studying the timings of an SD
|
||||
card for my Z80 computer.
|
||||

|
||||
|
||||
###Mac mini headset adapter
|
||||
I use a Mac mini (mid 2011) as my main desktop computer. I wanted
|
||||
to connect a headset to it for skyping, but found out that the only
|
||||
connections are Audio out and Line in.
|
||||
|
||||
The Audio out port, however, allows you to connect an
|
||||
iPhone headset and use its microphone with the computer.
|
||||
That gave me an idea, and after some searching I found
|
||||
[this](http://benttronics.blogspot.se/2009/05/audio-breakout-cable-for-i
|
||||
podiphone.html).
|
||||
|
||||
Once again I heated the soldering iron and got this:
|
||||

|
||||
|
||||
Let me tell you, that four-pole connector is one tight fit.
|
||||
|
||||
The top two connectors are wired in parallell and allows me to have both
|
||||
my speakers and my headset connected at the same time. It's easy enough
|
||||
to just turn off the speakers when I don't need them and I wont ever
|
||||
have to dig around behind the computer to connect the headset.
|
189
pages/2013-06-06-Even-More-Memory.md
Normal file
@ -0,0 +1,189 @@
|
||||
layout: post
|
||||
title: "Even More Memory"
|
||||
subtitle: "And processes again"
|
||||
tags: [osdev]
|
||||
|
||||
I did say memory was an important part of what the operating system
|
||||
does. Here's another post on it.
|
||||
|
||||
###Forking a process
|
||||
By using a system call a process may __fork__ i.e. create a copy of
|
||||
itself.
|
||||
|
||||
Right after the fork, the entire memory space should look the same to
|
||||
both the parent and child. However, if one changes something, the change
|
||||
should not affect the other.
|
||||
|
||||
For example:
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int variable = 1;
|
||||
int pid = fork();
|
||||
int status;
|
||||
|
||||
if( pid )
|
||||
{
|
||||
// This is the parent
|
||||
printf("Parent says: %d\n", variable);
|
||||
variable = 2;
|
||||
printf("Parent says: %d\n", variable);
|
||||
waitpid(pid, &status, 0); // Let the child run
|
||||
printf("Parent says: %d\n", variable);
|
||||
} else {
|
||||
// This is the child
|
||||
printf("Child says: %d\n", variable);
|
||||
variable = 3;
|
||||
printf("Child says: %d\n", variable);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
{: .lang-c}
|
||||
|
||||
This simple program should output (assuming the parent is run first and
|
||||
is not interrupted):
|
||||
|
||||
Parent says: 1
|
||||
Parent says: 2
|
||||
Child says: 1
|
||||
Child says: 3
|
||||
Parent says: 2
|
||||
|
||||
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.
|
||||
|
||||
The naive way to implement this functionality is to make a copy of the
|
||||
entire memory space, page for page, during the fork and assign it to the
|
||||
child process. However, it is common for a new process to start its life
|
||||
by performing `execve()` or similar and thus clearing or replacing its
|
||||
entire memory space. Obviously this would make copying the memory space
|
||||
just a waste of time.
|
||||
|
||||
A way to prevent this waste is to make all points on the two processes
|
||||
memory maps point to the same physical memory as long as they only read.
|
||||
A copy is made only if either process wishes to write to a memory area.
|
||||
This method is known as _Copy on Write_.
|
||||
|
||||
Now, I never took any courses in data structures and I'm sure there's
|
||||
better ways of doing this, but here's how I keep track of the memory
|
||||
areas used and shared between processes.
|
||||
|
||||
###Process memory map
|
||||
|
||||
In the kernel, each process has an associated _memory map_. The memory
|
||||
map contains information about the _memory areas_ of the process.
|
||||
|
||||
Each memory area describes a part of the process' memory. The size of
|
||||
the part can go from `0` to the entire memory space. The memory area
|
||||
structure thus has one field for the starting address of the area and
|
||||
one for the end address. It also has a field describing the area type
|
||||
and a flags field.
|
||||
|
||||
Each memory area belong in two doubly linked lists. One is the list of
|
||||
memory areas belonging to a process. The other list is all copies of the
|
||||
area.
|
||||
|
||||
Finally, each area has a pointer to its owning process.
|
||||
|
||||
Let's follow a memory area during part of a process' life.
|
||||
|
||||
###Setup
|
||||
{: .center .noborder}
|
||||
|
||||
In the figure above we see two processes, _A_ and _B_.
|
||||
Let's say _A_ is the _init_ process and _B_ is a shell.
|
||||
|
||||
Currently, _A_ has four memory areas and _B_ has three. As you can see,
|
||||
all the processes memory areas are linked together in a list (blue
|
||||
arrows). _A_ and _B_ also share one area and the two representations are
|
||||
linked together in another list (green arrows). The shared areas are
|
||||
flagged as "Copy on Write" (red color).
|
||||
|
||||
We'll be following the rightmost memory area of process _B_. This area
|
||||
represents the stack and stretches from `0xBFFFE000` to `0xC0000000`.
|
||||
In other words, it is two memory pages long (assuming 4kb pages).
|
||||
|
||||
###Forking
|
||||
|
||||
The user types
|
||||
|
||||
> gcc hello_world.c
|
||||
|
||||
into the terminal and the shell program executes the `fork` system call.
|
||||
|
||||
This makes the kernel do a lot of things, one of which is create a new
|
||||
memory map for the new process. It then clones all memory areas into the
|
||||
new map.
|
||||
{: .center .noborder}
|
||||
|
||||
The write flag of our area is unset and the CoW flag is set. The area is
|
||||
then copied into the new map and the copies list is updated so that our
|
||||
area and its copy can keep track of each other.
|
||||
|
||||
###Pushing to stack
|
||||
Let's say the scheduler returns us to the child process (process _C_)
|
||||
when the fork is complete.
|
||||
|
||||
The child process does some processing on the user entered command and
|
||||
during this tries to write a value to the stack. Since the area
|
||||
containing the stack is read-only this results in a page fault.
|
||||
|
||||
The page fault handler recognizes that the fault was caused by an
|
||||
attempt to write to a read-only page and by a quick check finds out that
|
||||
the area in question is marked for CoW, so it decides to make a copy of
|
||||
it. There's no need to copy both pages, though, so the memory manager
|
||||
first splits the area in two.
|
||||
|
||||
The same split is made in all copies of the same area. Finally, the area
|
||||
we want is physically copied and write is enabled on it before control
|
||||
is returned to the user process.
|
||||
|
||||
{: .center .noborder}
|
||||
|
||||
A while later, the parent process is scheduled in and it may also wish
|
||||
to write to the stack. This time the area is already split in two, and
|
||||
the required area has no copies, so it is just set as read/write and
|
||||
we're done.
|
||||
{: .center .noborder}
|
||||
|
||||
Actually, the parent process will probably perform a `waitpid` syscall
|
||||
and sleep untill the child has finished, so let's go back to the child.
|
||||
|
||||
###Exiting
|
||||
When the child process finishes, it frees all memory areas. When a page
|
||||
marked CoW is requested, the first check performed by the kernel is
|
||||
wether there actually are any other processes sharing the same area.
|
||||
Otherwise, it just marks it as read/write and is done. Therefore, all
|
||||
the child process needs to do is remove its own memory areas from the
|
||||
list of copies and the parent will take care of the rest.
|
||||
|
||||
###Zero size areas
|
||||
I said before that an area could have a zero size, i.e. the same start
|
||||
and end address.
|
||||
|
||||
This is only useful in combination with a certain flag that allows the
|
||||
area to grow automatically.
|
||||
|
||||
It could for example be used by a stack area which might originally
|
||||
start at `0xC0000000` and end at `0xC0000000`. If an `uint32_t` is
|
||||
pushed, the process will try to write to address `0xBFFFFFFC` which
|
||||
results in a page fault.
|
||||
|
||||
The page fault handler will realize that theres a memory area right
|
||||
above the address (say less than one page away) and that this area has
|
||||
the autogrow flag enabled. It will then just expand the area and be
|
||||
done.
|
||||
|
||||
The pros of this method is that we will never have to guess how large
|
||||
the stack size should be. It will grow as neede (to an extent) or stay
|
||||
at zero size if it's not needed.
|
||||
|
||||
###Git
|
||||
The methods described in this post has been implemented in git commit
|
||||
[cea5ec765f](https://github.com/thomasloven/os5/tree/cea5ec765ff683dbcf3
|
||||
116006c43a195245d9d6e).
|
119
pages/2013-06-07-System-Calls.md
Normal file
@ -0,0 +1,119 @@
|
||||
layout: post
|
||||
title: "System calls"
|
||||
subtitle: "Bend the stack to your will"
|
||||
tags: [osdev]
|
||||
|
||||
System calls is the way user processes communicate to the kernel. Look
|
||||
at the following program, for example.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
printf("Hello, world!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
{: .lang-c}
|
||||
|
||||
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
|
||||
then makes several more system calls before the `write()` and `exit()`
|
||||
system calls represented by the two lines in the code.
|
||||
|
||||
System calls can be performed in several ways, but one of the most
|
||||
common is through a special software interrupt with the `int`
|
||||
instruction. For example, linux and most unix-like hobby kernels I've
|
||||
studied use `int 0x80`. That's also what I chose to use in my kernel.
|
||||
|
||||
Next is the problem of passing data. The simplest way is using
|
||||
registers, and that's what most projects seem to use. For this, I chose
|
||||
a combination of a single register and the processes own stack.
|
||||
|
||||
###Sample system call
|
||||
Let's look at how `read()` would be implemented. I've not actually
|
||||
implemented it in my kernel yet, but here's how it would work.
|
||||
|
||||
####User side
|
||||
First the definition in the c library:
|
||||
|
||||
int read(int file, char *ptr, int len)
|
||||
{
|
||||
return _syscall_read(file, ptr, len);
|
||||
}
|
||||
|
||||
Simply a wrapper for an assembly function:
|
||||
|
||||
[global _syscall_read]
|
||||
_syscall_read:
|
||||
mov eax, SYSCALL_READ
|
||||
int 0x80
|
||||
mov [syscall_error], edx
|
||||
ret
|
||||
{: .lang-nasm}
|
||||
|
||||
This function puts an identifier for the system call in the `eax`
|
||||
register and then execute the system call interrupt.
|
||||
|
||||
_Note:_ Here I return the error code through register
|
||||
`edx`. In the actual code at this point, I used the
|
||||
register `ebx`. I should have looked up [Calling
|
||||
Conventions](http://wiki.osdev.org/Calling_Conventions) more carefully.
|
||||
|
||||
Of course, this can be simplified with a macro to
|
||||
|
||||
[global _syscall_read]
|
||||
DEF_SYSCALL(read, SYSCALL_READ)
|
||||
{: .lang-nasm}
|
||||
|
||||
####Kernel side
|
||||
|
||||
In the kernel, the system call is caught by the following function:
|
||||
|
||||
registers_t *syscall_handler(registers_t *r)
|
||||
{
|
||||
if(syscall_handlers[r->eax])
|
||||
r = syscall_handlers[r->eax](r);
|
||||
else
|
||||
r->edx = ERR_NOSYSCALL;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
If the system call is registered correctly in the kernel (through the
|
||||
macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything
|
||||
onto the following function:
|
||||
|
||||
KDEF_SYSCALL(read, r)
|
||||
{
|
||||
process_stack stack = init_pstack();
|
||||
|
||||
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
|
||||
r->edx = errno;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
The `init_pstack()` macro expands to `(unitptr_t *)(r->useresp + 0x4)`
|
||||
and this lets us read the arguments passed to the system call from where
|
||||
they are pushed on call.
|
||||
|
||||
Then the `read()` function has the same definition as the library version.
|
||||
|
||||
int read(int file, char *ptr, int len)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
_Spoiler alert:_ Keeping a version of `read()` (and in fact every
|
||||
syscall function) inside the kernel will turn out to have some really
|
||||
cool advantages...
|
||||
|
||||
This works for c compiled with the `cdecl` calling convention. For other
|
||||
languages or calling conventions, the asm functions will have to be
|
||||
adjusted.
|
||||
|
||||
###Git
|
||||
The methods described in this post has been implemented in git commit
|
||||
[8a26e26163](https://github.com/thomasloven/os5/tree/8a26e26163c15c9d9854554dce9d4fc5ad8baee5).
|
||||
|
195
pages/2013-08-20-Catching-Up.md
Normal file
@ -0,0 +1,195 @@
|
||||
layout: post
|
||||
title: "Catching Up"
|
||||
subtitle: "with a new toolchain"
|
||||
tags: [osdev]
|
||||
|
||||
Well... I've been bad at updating the blog on my process again...
|
||||
|
||||
Last time I missed updating, I made a branch from an earlier git commit,
|
||||
and threw away a bit of work and messy code rather than trying to catch
|
||||
up.
|
||||
|
||||
This time, however, I'm actually quite happy that I didn't write too
|
||||
much, because I've recently changed a lot of what I would have written
|
||||
about.
|
||||
|
||||
So instead, I'll describe one of my most recent changes here - a new
|
||||
cross compiler and a newlib port - and then in subsequent posts go over
|
||||
chosen parts of the other things I've done.
|
||||
|
||||
So let's jump into it.
|
||||
|
||||
### Why a cross compiler
|
||||
The be-or-not-to-be of cross compilers has been discussed at
|
||||
length and it seems every time someone posts a description
|
||||
on how to build one they are told that you don't need one if
|
||||
you know how to use your compiler or whatever. For examples,
|
||||
read
|
||||
[the osdev wiki - Why do I need a Cross Compiler? talk page](http://wiki.osdev.org/Talk:Why_do_I_need_a_Cross_Compiler%3F)
|
||||
(I don't recommend it if you're in a bad mood).
|
||||
|
||||
Well, I'm developing on OSX but have chosen to build an X86 kernel
|
||||
in elf format due to the availability of documentation, support and
|
||||
expertize. That's reason enough for a cross compiler build of binutils
|
||||
at least.
|
||||
|
||||
Clang offers excellent support for cross compilation out of the box, so
|
||||
I didn't use to have a need for a gcc cross compiler as illustrated in
|
||||
[this post](/blog/2013/02/New-Environment/). As it turns out, however,
|
||||
compiling newlib with that setup is rather annoying.
|
||||
|
||||
I'm also a fan of clean makefiles. Take a look at this:
|
||||
|
||||
VPATH := ../src
|
||||
|
||||
CC := i586-pc-myos-gcc
|
||||
|
||||
TARGETS := $(shell find ../src -name "*.c")
|
||||
TARGETS := $(notdir $(TARGETS))
|
||||
TARGETS := $(patsubst %.c, %, $(TARGETS))
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
clean:
|
||||
-rm $(TARGETS) 2>/dev/null
|
||||
{: .lang-make}
|
||||
|
||||
That's the makefile for the entire `/bin` directory in my os.
|
||||
|
||||
So let's move on...
|
||||
|
||||
### Building newlib and gcc
|
||||
For my toolchain build, I followed the tutorial at
|
||||
[the osdev wiki](http://wiki.osdev.org/OS_Specific_Toolchain) with a few
|
||||
changes. I chose to build everything inside the scope of
|
||||
[Homebrew](http://brew.sh) to keep track of all files and eventually
|
||||
make a formula for it. So after applying the patches described in the
|
||||
post (I even kept the name `i586-pc-myos` since I don't have a working
|
||||
name for my kernel besides an iteration number...) I did
|
||||
|
||||
export TARGET=i586-pc-myos
|
||||
export PREFIX=/usr/local/Cellar/osdev/1.0
|
||||
# Configure, build and install binutils
|
||||
brew link osdev
|
||||
# Configure, build and install gcc and libgcc
|
||||
brew unlink osdev
|
||||
brew link osdev
|
||||
|
||||
And that prepared me for building newlib.
|
||||
|
||||
### Building newlib
|
||||
Now this was an adventure...
|
||||
|
||||
Newlib uses a directory structure that hasn't been supported by
|
||||
automake and autoconf since a couple of versions. More specifically,
|
||||
you need automake version 1.12 or earlier and autoconf version 2.64.
|
||||
Unfortunately, those versions are not available through Homebrew, so ...
|
||||
|
||||
curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
|
||||
tar -zxf automake-1.12.tar.gz
|
||||
mkdir -p build-automake
|
||||
pushd build-automake
|
||||
../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
|
||||
make all -j
|
||||
make install
|
||||
popd
|
||||
curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
|
||||
tar -zxf autoconf-2.64.tar.gz
|
||||
pushd build-autoconf
|
||||
../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
|
||||
make all -j
|
||||
make install
|
||||
popd
|
||||
|
||||
brew switch automake 1.12
|
||||
brew switch autoconf 2.64
|
||||
|
||||
Those last two lines tells Homebrew that you want to use those specific
|
||||
versions for now.
|
||||
|
||||
Now for the neat part. I followed the wiki post and used the
|
||||
[syscall interface](/blog/2013/06/System-Calls) i've described earlier
|
||||
but I also wrapped `crt0.S` and `syscalls.c` in
|
||||
|
||||
#ifndef KERNEL_MODE
|
||||
...
|
||||
#endif
|
||||
|
||||
Then I built it all through
|
||||
|
||||
pushd build-newlib
|
||||
../newlib/configure --target=$TARGET --prefix=$PREFIX
|
||||
export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
|
||||
make -j
|
||||
make install
|
||||
mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
|
||||
rm -rf *
|
||||
../newlib/configure --target=$TARGET --prefix=$PREFIX
|
||||
export CPPFLAGS_FOR_TARGET=
|
||||
make -j
|
||||
make install
|
||||
popd
|
||||
|
||||
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
|
||||
kernel and allows me to use a standard C library with things like
|
||||
`sprintf` and even a ready made `malloc`.
|
||||
|
||||
"But wait," you say. "Doesn't malloc make an sbrk() syscall? You can't
|
||||
make a syscall from inside the kernel, can you?"
|
||||
|
||||
Well. Remember [my last post](/blog/2013/06/System-Calls) where I said
|
||||
that keeping versions of every syscall function inside the kernel would
|
||||
turn out to have some really cool advantages? Here they are.
|
||||
|
||||
I need an `sbrk` anyway, so all I have to do differently is make it
|
||||
check whether the request came from a user process or from the kernel
|
||||
and handle them a slight bit differently (i.e. make sure the `brk` is
|
||||
above `0xC0000000` for kernel and below for user mode...).
|
||||
|
||||
Similar checks can be made for all syscalls and some doesn't actually
|
||||
need any changing at all.
|
||||
|
||||
Needless to say, this revelation changed my kernel structure
|
||||
quite a bit and I entirely threw away my own
|
||||
[kernel heap manager](/blog/2012/07/Memory-Heap).
|
||||
|
||||
### Using this
|
||||
Having the cross compiler toolchain and library setup allows me to
|
||||
compile the kernel using a very simple makefile.
|
||||
|
||||
Somewhat simplified:
|
||||
|
||||
OBJECTS := $(shell find . -name "*.S")
|
||||
OBJECTS += $(shell find . -name "*.c")
|
||||
OBJECTS := $(OBJECTS:%.S=%.o)
|
||||
OBJECTS := $(OBJECTS:%.c=%.o)
|
||||
|
||||
CC := i586-pc-myos-gcc
|
||||
LDFLAGS := -nostdlib -T linkfile.ld
|
||||
LDLIBS := -lkernel
|
||||
|
||||
kernel: $(OBJECTS)
|
||||
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
||||
|
||||
and everything else is taken care of by the default rules of gnu make,
|
||||
including preprocessing and assembling .S files.
|
||||
|
||||
For executables running under my operating system it's even easier
|
||||
|
||||
CC := i586-pc-myos-gcc
|
||||
|
||||
That's all.
|
||||
|
||||
### GAS
|
||||
On a final note, the automatic rules for building object files out of .S
|
||||
files runs them through `CC`, which means they are assembled by
|
||||
`i586-pc-myos-as` which uses AT&T syntax rather than NASM which uses
|
||||
Intel syntax. I actually converted all my assembly code to AT&T syntax,
|
||||
but you might want to use the `.intel_syntax` directive instead.
|
||||
|
||||
|
||||
### Git
|
||||
The methods described in this post has been implemented in the directory
|
||||
`toolchain` of git commit
|
||||
[f09bd57b8e](https://github.com/thomasloven/os5/tree/f09bd57b8eb8c27607dab8250574c8eaed2939a0/toolchain).
|
160
pages/2013-08-21-Loading-Elf.md
Normal file
@ -0,0 +1,160 @@
|
||||
layout: post
|
||||
title: "Loading elf"
|
||||
subtitle: "there's DWARF in my ELF."
|
||||
tags: [osdev]
|
||||
|
||||
### Elf header format
|
||||
|
||||
Elf files all start with a header which identifies the file and explains
|
||||
where to find everything. It has the following structure. The
|
||||
[ELF specification](http://www.skyfree.org/linux/references/ELF_Format.pdf)
|
||||
gives an excellent description on the meaning and use of each field.
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t identity[16];
|
||||
uint16_t type;
|
||||
uint16_t machine;
|
||||
uint32_t version;
|
||||
uint32_t entry;
|
||||
uint32_t ph_offset;
|
||||
uint32_t sh_offset;
|
||||
uint32_t flags;
|
||||
uint16_t header_size;
|
||||
uint16_t ph_size;
|
||||
uint16_t ph_num;
|
||||
uint16_t sh_size;
|
||||
uint16_t sh_num;
|
||||
uint16_t strtab_index;
|
||||
}__attributes__((packed)) elf_header;
|
||||
|
||||
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
|
||||
file is located somewhere in memory and that this location is passed to
|
||||
the `load_elf()` function.)
|
||||
|
||||
To check if the file is an ELF executable we can look at the
|
||||
identity field. The first four bytes of this filed should always be
|
||||
`0x7F`,`'E'`,`'L'`,`'F'`. If that's correct, we can look at the `type`
|
||||
field. For an executable standalone program, this should be `2`.
|
||||
|
||||
int load_elf(uint8_t *data)
|
||||
{
|
||||
elf_header *elf = (elf_header *)data;
|
||||
if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
|
||||
return -1;
|
||||
...
|
||||
|
||||
`is_elf` looks as follows. Note the use of `strncmp` which I can do
|
||||
because I link [newlib into my kernel](/blog/2013/08/Catching-Up/).
|
||||
|
||||
int is_elf(elf_header *elf)
|
||||
{
|
||||
int iself = -1;
|
||||
|
||||
if((elf->identity[0] == 0x7f) && \
|
||||
!strncmp((char *)&elf->identity[1], "ELF", 3))
|
||||
{
|
||||
iself = 0;
|
||||
}
|
||||
|
||||
if(iself != -1)
|
||||
iself = elf->type;
|
||||
|
||||
return iself;
|
||||
}
|
||||
|
||||
Should be pretty straight forward. Let's continue.
|
||||
|
||||
For just loading a simple ELF program, we only need to look at the
|
||||
program headers which are located in a table at offset `ph_offset` in
|
||||
the file.
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t type;
|
||||
uint32_t offset;
|
||||
uint32_t virtual_address;
|
||||
uint32_t physical_address;
|
||||
uint32_t file_size;
|
||||
uint32_t mem_size;
|
||||
uint32_t flags;
|
||||
uint32_t align;
|
||||
}__attributes__((packed)) elf_phead;
|
||||
|
||||
The program headers each tell us about one section of the file, and we
|
||||
use them to find out what parts of the elf image should be loaded where
|
||||
in memory. So, the next step would be to go through all program headers
|
||||
looking for loadable sections and load them into memory.
|
||||
|
||||
...
|
||||
elf_phead *phead = (elf_phead)&data[elf->ph_offset];
|
||||
uint32_t i;
|
||||
for(i = 0; i < elf->ph_num; i++)
|
||||
{
|
||||
if(phead[i].type == ELF_PT_LOAD)
|
||||
{
|
||||
load_elf_segment(data, &phead[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
of code and data for example.
|
||||
|
||||
Anyway, `load_elf_segment()` looks like this
|
||||
|
||||
void load_elf_segment(uint8_t *data, elf_phead *phead)
|
||||
{
|
||||
|
||||
uint32_t memsize = phead->mem_size; // Size in memory
|
||||
uint32_t filesize = phead->file_size; // Size in file
|
||||
uint32_t mempos = phead->virtual_address; // Offset in memory
|
||||
uint32_t filepos = phead->offset; // Offset in file
|
||||
|
||||
uint32_t flags = MM_FLAG_READ;
|
||||
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE;
|
||||
|
||||
new_area(current->proc, mempos, mempos + memsize, \
|
||||
flags, MM_TYPE_DATA);
|
||||
|
||||
if(memsize == 0) return;
|
||||
|
||||
memcpy(mempos, &data[filepos], filesize);
|
||||
memset(mempos + filesize, 0, memsize - filesize);
|
||||
}
|
||||
|
||||
Let's go through it.
|
||||
|
||||
First we define some helper variables.
|
||||
|
||||
Next we check if the section we're loading should be writable.
|
||||
|
||||
Then we request a new memory area from the [process memory
|
||||
manager](/blog/2013/06/Even-More-Memory/).
|
||||
|
||||
Finally, we copy as much data as is provided in the file and fill the
|
||||
rest of the new area with zeros.
|
||||
|
||||
And that's really all you need to do to load an ELF executable.
|
||||
The only thing left is to jump to `elf->entry` and you're going.
|
||||
|
||||
### Improvements
|
||||
Of course the entire executable image won't be loaded into memory in the
|
||||
normal case, but it might be true for e.g. an `init` program or similar
|
||||
that your bootloaded loads as a module to your kernel. Instead, you
|
||||
should read the parts you want through your filesystem as you go along.
|
||||
|
||||
Or maybe you shouldn't. It doesn't make sense to load a huge program
|
||||
into memory all at once. What if it encounters an error and exits with
|
||||
99% of the code unexecuted?
|
||||
|
||||
Perhaps the process memory manager could be told where to find certain
|
||||
parts of the program, and load them only when needed?
|
||||
|
||||
### Git
|
||||
The methods described in this post has been implemented in git commit
|
||||
[a4ca835d1d](https://github.com/thomasloven/os5/tree/a4ca835d1db61faf214b4b617d38a335ef05d142).
|
||||
|
94
pages/2013-08-22-Virtual-File-System.md
Normal file
@ -0,0 +1,94 @@
|
||||
layout: post
|
||||
title: "Virtual File System"
|
||||
subtitle: "and a weird weird bug."
|
||||
tags: [osdev]
|
||||
|
||||
I was planning to start this post with talking about how I was not very
|
||||
satisfied with my VFS layer and how I would like to rewrite it. But then
|
||||
I rewrote it instead... and ran into a really weird bug.
|
||||
|
||||
### The bug
|
||||
I'll start with the bug, since it doesn't have anything to do with the
|
||||
VFS, but perhaps someone could help me if I write it down.
|
||||
|
||||
So, I was happily coding away when I decided to run my emulator as
|
||||
usual. Since I develop exclusively in a terminal environment right now
|
||||
my setup for testing is four panes in tmux.
|
||||
|
||||
- One pane runs qemu and displays its output.
|
||||
- One pane controls the qemu terminal through telnet.
|
||||
- One pane displays the serial output of qemu.
|
||||
- One pane runs gdb connected to qemu.
|
||||
|
||||
Qemu is setup to do nothing until I write 'c' in either the terminal or
|
||||
gdb to allow me to setup breakpoints and such. Only this time, the qemu
|
||||
pane closed immediately before I could do anything at all.
|
||||
|
||||
I tried again, with the same results. And again in a desperate display
|
||||
of what Einstein allegedly called madness.
|
||||
|
||||
What was the problem? It obviously couldn't be anything in my code. My
|
||||
code didn't even have time to start running!
|
||||
|
||||
After a while I reverted my recent changes anyway, and the testing setup
|
||||
ran flawlessly.
|
||||
|
||||
What? But my code didn't even run!
|
||||
|
||||
I reapplied my changes one at a time and found the line that caused the
|
||||
problem.
|
||||
|
||||
int i = 0;
|
||||
|
||||
What?? That's it? Declaring a variable?
|
||||
|
||||
Anyway. I changed my code about to do the same thing in another way.
|
||||
Debugging worked like a charm, but there was some unexpected behavior.
|
||||
The best way to nail it down seemed to be by printing a short string
|
||||
whenever a function was run. And qemu crashed
|
||||
|
||||
What??? Does my emulator hate running working code?
|
||||
|
||||
Ok. I've had problems with string before. I ran objdump, commented out
|
||||
the string and ran objdump again and compare the sections. They were
|
||||
probably overlapping some page border and causing some problems I didn't
|
||||
catch with my last linking script update. There really shouldn't be any
|
||||
problems left, but anyone can be wrong. Nothing obvious, though... but when I ran the emulator again, it
|
||||
crashed.
|
||||
|
||||
What???? But I commented the problem out. Didn't I?
|
||||
|
||||
Removing the commented line fixed the problem.
|
||||
|
||||
What????? A comment?
|
||||
|
||||
Next I tried running only qemu, without the other panes. It worked like
|
||||
a clock. Starting the terminal console; no problem. Starting gdb...
|
||||
crash! Hm...
|
||||
|
||||
Removing the `-ggdb` flag from my compiling worked. `-g1` worked `-g2`
|
||||
did not...
|
||||
|
||||
I have a `.gdbinit` file in my code root directory, which makes gdb load
|
||||
debug symbols and attach automatically. It also sets some useful debug
|
||||
breakpoints, like whenever I encounter unsolvable page faults. It turns
|
||||
out the breakpoints were the culprit. If I didn't set them, everything
|
||||
ran fine.
|
||||
|
||||
Now I was getting somewhere. I thought.
|
||||
|
||||
After running both gdb and qemu through gdb and actually plowing through
|
||||
the DWARF information from `objdump -w`. I finally gave up.
|
||||
|
||||
My conclusion:
|
||||
This is either a bug in gcc, a bug in gdb or a bug in qemu. To me the
|
||||
third alternative seems the likeliest by far.
|
||||
My guess is that there's a but in qemus gdb stubs.
|
||||
|
||||
If you heard about this bug or experienced it yourself or just have any
|
||||
guesses, please let me know.
|
||||
|
||||
### VFS
|
||||
Oops.. this grew long... Let's take the VFS some other time, shall we?
|
||||
|
||||
|
408
pages/2013-12-11-Virtual-File-System2.md
Normal file
@ -0,0 +1,408 @@
|
||||
layout: post
|
||||
title: "Virtual File System 2"
|
||||
subtitle: "for real this time."
|
||||
tags: [osdev]
|
||||
|
||||
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
|
||||
losses there, at least. Instead I've been busy with a special side
|
||||
project that I will write a bit about later and a lot of personal stuff
|
||||
that you probably don't care about if you're reading those posts. Unless
|
||||
you're my wife, that is (I got married! :) ). Then again, if you're my
|
||||
wife you're probably not reading those posts anyway, so I guess the
|
||||
entire point of this paragraph was to tell you I got married(!).
|
||||
|
||||
Ahem...
|
||||
|
||||
###The virtual filesystem
|
||||
So, as I said in [my last post](/blog/2013/08/Virtual-File-System/)
|
||||
I recently rewrote my VFS layer. Although, I must admit I'm not quite satisfied
|
||||
with it yet...
|
||||
|
||||
...
|
||||
|
||||
Know what? Let's rewrite it again!
|
||||
|
||||
###What I want
|
||||
First of all: What do I want from the virtual filesystem?
|
||||
|
||||
The VFS should be an abstraction layer for the files used by the kernel
|
||||
and user processes. The files in this case could be files on a disk,
|
||||
the disk hardware itself, pipes, files stored in ram, read-only files
|
||||
generated on-the-fly by the kernel, network connections(?) etc.
|
||||
|
||||
Further, I want the VFS to be independent of any disk file system, e.g.
|
||||
the VFS shouldn't have user-group-other read-write-execute tuples just
|
||||
because it's designed with ext2 in mind. It might have those tuples -
|
||||
I haven't decided yet - but it it does it won't be because ext2 uses
|
||||
them. Nor will the VFS be designed with ext2 or any other disk file
|
||||
system in mind.
|
||||
|
||||
The VFS should offer the functions
|
||||
|
||||
open() // Open or create a file
|
||||
close() // Close a file
|
||||
read() // Read data from opened file
|
||||
write() // Write data to opened file
|
||||
move() // Move a file or directory
|
||||
link() // Put a file or directory in path tree
|
||||
unlink() // Remove a file or directory from path tree
|
||||
stat() // Get more info about a file or directory
|
||||
isatty() // Returns true if the file is a terminal
|
||||
mkdir() // Create a directory
|
||||
readdir() // Get a directory entry
|
||||
finddir() // Find a file by name from a directory
|
||||
|
||||
for all files and directories regardless of their underlying device or
|
||||
driver.
|
||||
|
||||
The file system should have a tree structure with a single root.
|
||||
|
||||
Filesystems should be mountable at any path in the tree provided the
|
||||
path points to a directory or non-existing node within an existing
|
||||
parent directory. I.e. if the empty directory `/foo`, a filesystem
|
||||
can be mounted to `/foo` or `/foo/bar` but not to `foo/baz/bar`. If
|
||||
`/foo` is not empty, it should a filesystem can still be mounted to
|
||||
it, unless it is already a mountpoint. All contents of `/foo` are then
|
||||
hidden untill the filesystem is unmounted. Mounting filesystems to
|
||||
non-directories should not be possible. Mounted filesystems does not
|
||||
have to have a root directory, but can consist of a single file.
|
||||
|
||||
Those are just some rules for mounting that I thought of pretty much
|
||||
arbitrarily. I might change my mind later, but this will do for now.
|
||||
|
||||
|
||||
###Implementation
|
||||
The most important data structure of the VFS is the inode.
|
||||
Each file used by the kernel gets an inode which keeps track of some
|
||||
important information of it, such as what type of file it is or which
|
||||
driver controls it.
|
||||
|
||||
Some inodes live in the mount tree, which keeps track of all mounted
|
||||
filesystems. Looking up a file by absolute path always starts in the
|
||||
mount tree and is performed by a function called namei (name to inode).
|
||||
|
||||
Example:
|
||||
|
||||
- A user process wants the file `/mnt/floppy/foo/bar.txt`
|
||||
- `namei` starts at the VFS root /
|
||||
- `namei` searches for `/mnt` in the VFS tree, finds it and gets it's inode.
|
||||
- `namei` searches for `/mnt/floppy` in the VFS tree, finds it and gets it's inode.
|
||||
- `namei` searches for `/mnt/floppy/foo`, which is not found.
|
||||
- `namei` asks the `/mnt/floppy` inode for `/mnt/floppy/foo` and gets it's inode.
|
||||
- `namei` asks the `/mnt/floppy/foo` inode for `/mnt/floppy/foo/bar.txt` and gets it's inode.
|
||||
- `namei` returns the inode for `/mnt/floppy/foo/bar.txt`
|
||||
|
||||
A good starting point for the inode structure might be some pointers to
|
||||
allow it to be placed in a tree, then.
|
||||
|
||||
|
||||
struct vfs_node_st;
|
||||
typedef vfs_node_t * INODE;
|
||||
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
INODE parent;
|
||||
INODE child;
|
||||
INODE older, younger;
|
||||
uint32_t type;
|
||||
} vfs_node_t;
|
||||
|
||||
|
||||
|
||||
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,
|
||||
and it's far from the worst memory thief in this kernel anyway.
|
||||
|
||||
The `type` field in the VFS node struct is used by the VFS tree to make
|
||||
sure stuff is only mounted onto directories.
|
||||
|
||||
I typedef `INODE` as a pointer to keep the code a bit cleaner and easier
|
||||
to maintain.
|
||||
|
||||
|
||||
Then, we need a way to keep track of the driver, i.e. the functions
|
||||
called to access the file. To do this, I define a new struct:
|
||||
|
||||
typedef struct vfs_driver_st
|
||||
{
|
||||
uint32_t (*open)(INODE, uint32_t);
|
||||
uint32_t (*close)(INODE);
|
||||
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
|
||||
uint32_t (*link)(INODE, INODE, const char *);
|
||||
uint32_t (*unlink)(INODE, const char *);
|
||||
uint32_t (*stat)(INODE, struct stat *st);
|
||||
uint32_t (*isatty)(INODE);
|
||||
uint32_t (*mkdir)(INODE, const char *);
|
||||
dirent_t *(*readdir)(INODE, uint32_t);
|
||||
INODE (*finddir)(INODE, const char *);
|
||||
} vfs_driver_t;
|
||||
|
||||
and add `vfs_driver_t *d` to the inode struct. I also added a length
|
||||
value, a void pointer for arbitrary data used by the drivers and a flags
|
||||
value - also for use by the drivers. The
|
||||
inode struct now looks like this:
|
||||
|
||||
typedef struct vfs_node_st
|
||||
{
|
||||
char name[VFS_NAME_SZ];
|
||||
void *parent;
|
||||
void *child;
|
||||
void *older, *younger;
|
||||
uint32_t type;
|
||||
vfs_driver_t *d;
|
||||
void *data;
|
||||
uint32_t flags;
|
||||
uint32_t length;
|
||||
}
|
||||
|
||||
###Vfs functions
|
||||
Next, I create some wrapper functions to call the driver functions.
|
||||
|
||||
uint32_t vfs_open(INODE ino, uint32_t mode)
|
||||
{
|
||||
if(ino->d->open)
|
||||
return ino->d->open(ino, mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
and similar for all functions except `readdir` and `finddir` which
|
||||
contain code to handle `.` and `..` for mount roots.
|
||||
|
||||
|
||||
dirent_t *vfs_readdir(INODE ino, uint32_t num)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino;
|
||||
strcpy(ret->name, ".");
|
||||
return ret;
|
||||
} else if(num == 1) {
|
||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||
ret->ino = ino->parent;
|
||||
strcpy(ret->name, "..");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if(ino->d->readdir)
|
||||
return ino->d->readdir(ino, num);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
INODE vfs_finddir(INODE ino, const char *name)
|
||||
{
|
||||
if(ino->type & FS_MOUNT)
|
||||
{
|
||||
if(!strcmp(name, "."))
|
||||
{
|
||||
return ino;
|
||||
} else if(!strcmp(name, "..")) {
|
||||
return ino->parent;
|
||||
}
|
||||
}
|
||||
if(ino->d->finddir)
|
||||
return ino->d->finddir(ino, name);
|
||||
if(ino->d->readdir)
|
||||
{
|
||||
// Backup solution
|
||||
int num = 0;
|
||||
dirent_t *de;
|
||||
while(1)
|
||||
{
|
||||
de = vfs_readdir(ino, num);
|
||||
if(!de)
|
||||
return 0;
|
||||
if(!strcmp(name, de->name))
|
||||
break;
|
||||
free(de->name);
|
||||
free(de);
|
||||
num++;
|
||||
}
|
||||
INODE ret = de->ino;
|
||||
free(de->name);
|
||||
free(de);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Finally, I needed a function for mounting filesystems in the mount tree
|
||||
and the `namei` function, which can actually be combined since they both
|
||||
need to traverse the entire path.
|
||||
|
||||
_Warning:_ Pointer-pointers ahead!
|
||||
|
||||
First: a function for traversing the mount tree as far as possible
|
||||
|
||||
INODE vfs_find_root(char **path)
|
||||
{
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_root;
|
||||
INODE mount = current;
|
||||
char *name;
|
||||
while((name = strsep(path, "/")))
|
||||
{
|
||||
current = current->child;
|
||||
while(current)
|
||||
{
|
||||
if(!strcmp(current->name, name))
|
||||
{
|
||||
mount = current;
|
||||
break;
|
||||
}
|
||||
current = current->olderyounger;
|
||||
}
|
||||
if(!current)
|
||||
{
|
||||
if(*path)
|
||||
{
|
||||
*path = *path - 1;
|
||||
*path[0] = '/';
|
||||
}
|
||||
*path = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (INODE)mount;
|
||||
}
|
||||
|
||||
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`
|
||||
pointer. Then, for each part, we look through the children of the node
|
||||
we're at for one with the right name. If it is not found, the path
|
||||
pointer is backed up one step and the last node we found is returned.
|
||||
|
||||
The namei/mount function then uses this as a starting point:
|
||||
|
||||
|
||||
|
||||
INODE vfs_namei_mount(const char *path, INODE root)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
// Find closest point in mount tree
|
||||
INODE current = vfs_find_root(&pth);
|
||||
char *name;
|
||||
while(current && (name = strsep(&pth, "/")))
|
||||
{
|
||||
// Go through the path
|
||||
INODE next = vfs_finddir(current, name);
|
||||
|
||||
if(root)
|
||||
{
|
||||
// If we want to mount someting
|
||||
if(!next)
|
||||
{
|
||||
// Create last part of path if it doesn't exist
|
||||
// But only if it is the last part.
|
||||
if(pth)
|
||||
return 0;
|
||||
next = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(next->name, name);
|
||||
next->type = FS_DIRECTORY;
|
||||
}
|
||||
|
||||
// Add path to mount tree
|
||||
next->parent = current;
|
||||
next->older = current->child;
|
||||
current->child = next;
|
||||
}
|
||||
|
||||
if(!next)
|
||||
return 0;
|
||||
if(!current->parent)
|
||||
free(current);
|
||||
|
||||
current = next;
|
||||
}
|
||||
free(npath);
|
||||
|
||||
if(root && current->type == FS_DIRECTORY)
|
||||
{
|
||||
// Replace node in mount tree
|
||||
root->parent = current->parent;
|
||||
if(root->parent->child == current)
|
||||
root->parent->child = root;
|
||||
root->older = current->older;
|
||||
if(root->older)
|
||||
root->older->younger = current;
|
||||
root->younger = current->younger;
|
||||
if(root->younger)
|
||||
root->younger->older = current;
|
||||
strcpy(root->name, current->name);
|
||||
root->type = FS_MOUNT;
|
||||
if(current == vfs_root)
|
||||
vfs_root = root;
|
||||
|
||||
free(current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
{: .lang-c}
|
||||
|
||||
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
|
||||
until we reach the target or a dead end. If the dead end is at the very
|
||||
last part of the path (`/foo/bar` in the example above) and we want to
|
||||
mount something a new node is created. Otherwise the function returns.
|
||||
Also, if the goal is to mount something, each part of the path is added
|
||||
to the mount tree. Finally, the mounting is performed - if requested and
|
||||
the final inode is returned.
|
||||
|
||||
I also made two simple wrappers for this function:
|
||||
|
||||
|
||||
INODE vfs_namei(const char *path)
|
||||
{
|
||||
return vfs_namei_mount(path, 0);
|
||||
}
|
||||
|
||||
INODE vfs_mount(const char *path, INODE root)
|
||||
{
|
||||
return vfs_namei_mount(path, root);
|
||||
}
|
||||
{: .lang-c}
|
||||
|
||||
And finally, a function for unmounting file systems:
|
||||
|
||||
|
||||
INODE vfs_umount(const char *path)
|
||||
{
|
||||
char *npath = strdup(path);
|
||||
char *pth = &npath[1];
|
||||
INODE ino = vfs_find_root(&pth);
|
||||
if(!ino || pth)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
}
|
||||
if(ino->child)
|
||||
{
|
||||
free(npath);
|
||||
return 0;
|
||||
} else {
|
||||
// Remove node from mount tree
|
||||
if(ino->parent->child == ino)
|
||||
ino->parent->child = ino->older;
|
||||
if(ino->younger)
|
||||
ino->younger->older = ino->older;
|
||||
if(ino->older)
|
||||
ino->older->younger = ino->younger;
|
||||
free(npath);
|
||||
return ino;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
commit link.
|
||||
|
||||
Next time, I'll look at some file related system calls.
|
196
pages/2013-12-12-VFS-syscalls.md
Normal file
@ -0,0 +1,196 @@
|
||||
title: "VFS syscalls"
|
||||
subtitle: "...so I put wrappers 'round your wrappers..."
|
||||
tags: [osdev]
|
||||
|
||||
[Last time](/blog/2013/12/Virtual-File-System2/) I started to rewrite
|
||||
the VFS layer of my hobby kernel - again. This time I'll take a look at
|
||||
the system call couplings.
|
||||
|
||||
Since a [while](/blog/2013/08/Catching-Up/), I've had a cross compiler
|
||||
and [newlib](http://sourceware.org/newlib) for my kernel, which means I
|
||||
have some basic syscall interfaces to start from.
|
||||
|
||||
Newlib requires the following syscalls:
|
||||
|
||||
:::c
|
||||
int close(int file)
|
||||
int fstat(int file, struct stat *st)
|
||||
int isatty(int file)
|
||||
int link(char *old, char *new)
|
||||
int lseek(int file, int ptr, int dir)
|
||||
int open(const char *name, int flags, int mode)
|
||||
int read(int file, char *ptr, int len)
|
||||
int stat(const char *file, struct stat *st)
|
||||
int unlink(char *name)
|
||||
int write(int file, char *ptr, int len)
|
||||
|
||||
###open and close
|
||||
Everything starts with `open`, so let's look at that first.
|
||||
|
||||
In order to keep track of the files that are opened by a process, we
|
||||
need a new data structure, though; the _file descriptor_.
|
||||
|
||||
:::c
|
||||
typedef struct
|
||||
{
|
||||
INODE ino;
|
||||
uint32_t offset;
|
||||
uint32_t flags;
|
||||
uint32_t users;
|
||||
} file_desc_t;
|
||||
|
||||
The file descriptor keeps track of our position in the file as well as
|
||||
the mode it was opened in. File descriptors can also be shared between
|
||||
processes (after a `fork()` for example), and it therefore has a use
|
||||
counter. Two macros are used to manipulate the use counter
|
||||
|
||||
:::c
|
||||
#define fd_get(fd) { (fd)->users++ }
|
||||
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
|
||||
|
||||
Each process descriptor has an array of pointers to file descriptors
|
||||
|
||||
:::c
|
||||
file_desc_t *fd[NUM_FILEDES];
|
||||
|
||||
`open` starts by finding a free file descriptor. It then finds the file,
|
||||
opens the file and returns the index of the file descriptor it used:
|
||||
|
||||
:::c
|
||||
int open(const char *name, int flags, int mode)
|
||||
{
|
||||
int fd;
|
||||
|
||||
// Find unused file descriptor
|
||||
process_t *p = current->proc;
|
||||
int i;
|
||||
for(i=0; i < NUM_FILEDES; i++)
|
||||
{
|
||||
if(p->fd[i])
|
||||
continue;
|
||||
fd = i;
|
||||
p->fd[fd] = calloc(1, sizeof(file_desc_t));
|
||||
fd_get(p->fd[fd]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find file
|
||||
INODE ino = vfs_namei(name);
|
||||
// Open file
|
||||
vfs_open(name, flags);
|
||||
|
||||
// Setup file descriptor
|
||||
p->fd[fd]->ino = ino;
|
||||
p->fd[fd]->offset = 0;
|
||||
p->fd[fd]->flags = flags;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
I stripped away all of the sanity checking and error handling code here.
|
||||
With that code, the function is more than twice as long.
|
||||
|
||||
`close` is even easier:
|
||||
|
||||
:::c
|
||||
int close(int file)
|
||||
{
|
||||
int retval = vfs_close(p->fd[file]->ino);
|
||||
if(!p->fd[file]->ino->parent)
|
||||
free(p->fd[file]->ino);
|
||||
fd_put(p->fd[file]);
|
||||
p->fd[file] = 0;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
###read and write
|
||||
Next, let's look at read.
|
||||
It's actually really simple (excluding sanity checking and error
|
||||
handling):
|
||||
|
||||
:::c
|
||||
int read(int file, char *ptr, int len)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
INODE node = p->fd[file]->ino;
|
||||
int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
|
||||
p->fd[file]->offset += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Write is pretty much the same:
|
||||
|
||||
:::c
|
||||
int write(int file, char *ptr, int len)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
INODE node = p->fd[file]->ino;
|
||||
int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
|
||||
p->fd[file]->offset += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
###stat, fstat and isatty
|
||||
`fstat` and `isatty` just passes on the information to the corresponding
|
||||
vfs functions:
|
||||
|
||||
:::c
|
||||
int fstat(int file, struct stat *st)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
INODE node = p->fd[file]->ino;
|
||||
return vfs_fstat(node, st);
|
||||
}
|
||||
|
||||
:::c
|
||||
int isatty(int file)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
INODE node = p->fd[file]->ino;
|
||||
return vfs_isatty(node);
|
||||
}
|
||||
|
||||
`stat` performs a `namei` lookup to get the node instead of taking it
|
||||
from the process' file descriptor table.
|
||||
|
||||
:::c
|
||||
int stat(const char *file, struct stat *st)
|
||||
{
|
||||
INODE node = vfs_namei(file);
|
||||
int retval = vfs_fstat(node, st);
|
||||
if(!node->parent)
|
||||
free(node);
|
||||
return retval;
|
||||
}
|
||||
|
||||
###lseek
|
||||
The final function I'll look at now is `lseek` which sets the current
|
||||
position in the file:
|
||||
|
||||
:::c
|
||||
int lseek(int file, int ptr, int dir)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
if(dir == SEEK_SET)
|
||||
{
|
||||
p->fd[file]->offset = ptr;
|
||||
}
|
||||
if(dir == SEEK_CUR)
|
||||
{
|
||||
p->fd[file]->offset += ptr;
|
||||
}
|
||||
if(dir == SEEK_END)
|
||||
{
|
||||
p->fd[file]->offset = p->fd[file]->ino->length + ptr;
|
||||
}
|
||||
return p->fd[file]->offset;
|
||||
}
|
||||
|
||||
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).
|
||||
|
||||
In the next post, we'll mount an actual filesystem.
|
95
pages/2013-12-13-Debug-Filesystem.md
Normal file
@ -0,0 +1,95 @@
|
||||
layout: post
|
||||
title: "The debug file system"
|
||||
subtitle: "It's still in the kernel!"
|
||||
tags: [osdev]
|
||||
|
||||
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.
|
||||
|
||||
The filesystem I want to start with is what I call the `debug` file.
|
||||
|
||||
It is a single file, which mounts to `/` for now (later `/dev/debug`)
|
||||
and cannot be read from. Writing to it will print the written text on
|
||||
the screen.
|
||||
|
||||
Three functions are needed for newlib `fprintf` to be able to write to
|
||||
it: `stat`, `isatty` and `write`. Those are all rather simple, since
|
||||
they won't need to keep track of which file we're referencing.
|
||||
|
||||
uint32_t debug_stat(INODE node, struct stat *st)
|
||||
{
|
||||
memset(st, 0, sizeof(struct stat));
|
||||
st->st_mode = S_IFCHR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
I don't care much about the stat for the debug file. Maybe I'll add some
|
||||
creation time or so later...
|
||||
|
||||
uint32_t debug_isatty(INODE node)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
The debug output is a terminal.
|
||||
|
||||
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
||||
{
|
||||
char *buf = calloc(size + 1, 1);
|
||||
memcpy(buf, buffer, size);
|
||||
kdbg_puts(buf);
|
||||
free(buf);
|
||||
return size;
|
||||
}
|
||||
|
||||
`kdbg_puts` is a function I wrote
|
||||
[a long time ago](/blog/2012/06/Kernel-Debug-Functions/) which prints a
|
||||
string to the screen. The first two lines are just to make sure that the
|
||||
data is null-terminated.
|
||||
|
||||
With this, I can define a driver for the "debug device":
|
||||
|
||||
vfs_driver_t debug_driver =
|
||||
{
|
||||
0, // open
|
||||
0, // close
|
||||
0, // read
|
||||
debug_write, // write
|
||||
0, // link
|
||||
0, // unlink
|
||||
debug_stat, // stat
|
||||
debug_isatty, // isatty
|
||||
0, // mkdir
|
||||
0, // readdir
|
||||
0 // finddir
|
||||
};
|
||||
|
||||
And then write a function to setup the device:
|
||||
|
||||
INODE debug_dev_init()
|
||||
{
|
||||
INODE node = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(node->name, "debug");
|
||||
node->d = &debug_driver;
|
||||
node->type = FS_CHARDEV;
|
||||
return node;
|
||||
}
|
||||
|
||||
Then, to activate it, all I need to do is add
|
||||
|
||||
vfs_init();
|
||||
vfs_mount("/", debug_dev_init());
|
||||
|
||||
in my kernel boot code. After that I can use the standard library
|
||||
functions:
|
||||
|
||||
FILE *dbg = fopen("/", "w");
|
||||
fprintf(dbg, "Hello, world!\n");
|
||||
|
||||
or even:
|
||||
|
||||
fopen("/", "r");
|
||||
fopen("/", "w");
|
||||
printf("Hello, world!\n");
|
||||
|
||||
That's it for this time. Next time, we'll do some piping!
|
||||
|
185
pages/2013-12-18-Pipes.md
Normal file
@ -0,0 +1,185 @@
|
||||
layout: post
|
||||
title: "Pipes"
|
||||
subtitle: "... and keyboard."
|
||||
tags: [osdev]
|
||||
|
||||
In most unix-like systems, pipes can be used to make processes
|
||||
communicate with each other. To the processes the pipe looks just like
|
||||
any other file, but when one process tries to read from it, it will get
|
||||
the data the other process wrote.
|
||||
|
||||
With a couple of hints from the people at #osdev on irc.freenode.net,
|
||||
I finally got pipes to work - although a bit inefficiently - the way I
|
||||
wanted a few days ago.
|
||||
|
||||
I implemented pipes as circular buffers, which makes them a perfect
|
||||
example of the "producer-consumer problem" which is well known in
|
||||
operating system theory. The schoolbook solution to the problem uses
|
||||
sleeping processes in roughly the following way:
|
||||
|
||||
Producer/Writer:
|
||||
|
||||
- Write until all bytes are written or until the buffer is full.
|
||||
- Wake any processes that are sleeping on the buffer.
|
||||
- If you have more bytes to write, go to sleep on the buffer and then
|
||||
repeat.
|
||||
|
||||
Consumer/Reader:
|
||||
|
||||
- Read until all bytes are read or until the buffer is empty.
|
||||
- Wake any processes that are sleeping on the buffer.
|
||||
- If there were no bytes to read, go to sleep on the buffer and then
|
||||
repeat.
|
||||
|
||||
In code this translates to
|
||||
|
||||
uint32_t pipe_write(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
||||
{
|
||||
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
||||
char *buf = (char *)buffer;
|
||||
uint32_t bytes_written = 0;
|
||||
while(bytes_written < size)
|
||||
{
|
||||
while((pipe->write_pos - pipe->read_pos) < pipe->size && bytes_written < size)
|
||||
{
|
||||
pipe->buffer[pipe->write_pos % pipe->size] = buf[bytes_written];
|
||||
bytes_written++;
|
||||
pipe->write_pos++;
|
||||
}
|
||||
scheduler_wake(&pipe->waiting);
|
||||
if(bytes_written < size)
|
||||
{
|
||||
scheduler_sleep(current, &pipe->waiting);
|
||||
schedule();
|
||||
}
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
uint32_t pipe_read(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
||||
{
|
||||
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
||||
char *buf = (char *)buffer;
|
||||
uint32_t bytes_read = 0;
|
||||
while(bytes_read == 0)
|
||||
{
|
||||
while((pipe->write_pos - pipe->read_pos) > 0 && bytes_read < size)
|
||||
{
|
||||
buf[bytes_read] = pipe->buffer[pipe->read_pos % pipe->size];
|
||||
bytes_read++;
|
||||
pipe->read_pos++;
|
||||
}
|
||||
scheduler_wake(&pipe->waiting);
|
||||
if(bytes_read == 0)
|
||||
{
|
||||
scheduler_sleep(current, &pipe->waiting);
|
||||
schedule();
|
||||
}
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
Of course there should also be:
|
||||
|
||||
- Sanity checking.
|
||||
- A lock to prevent reading and writing at the same time.
|
||||
- Handling of the cases where there are no readers or no writers.
|
||||
|
||||
Note that the reader only blocks if no bytes at all have been read,
|
||||
while the writer blocks if not all bytes have been written.
|
||||
|
||||
Creating a new pipe is a bit special, though.
|
||||
An ordinary pipe has two ends, one for writing and one for reading. The
|
||||
best way I found to implement this is to have the pipe represented by
|
||||
two separate inodes - one that can be written to and one that can be
|
||||
read from. The `data` field of the `vfs_node_st` struct of the two
|
||||
inodes point to the same pipe struct.
|
||||
|
||||
typedef struct vfs_pipe
|
||||
{
|
||||
char *buffer;
|
||||
uint32_t size;
|
||||
uint32_t read_pos;
|
||||
uint32_t write_pos;
|
||||
uint32_t readers;
|
||||
uint32_t writers;
|
||||
semaphore_t semaphore;
|
||||
list_head_t waiting;
|
||||
} vfs_pipe_t;
|
||||
|
||||
The `readers` and `writers` fields are incremented or decremented when the read or
|
||||
write end respectively are opened or closed... respectively...
|
||||
|
||||
Creating a new pipe (somewhat simplified):
|
||||
|
||||
uint32_t new_pipe(uint32_t size, INODE *nodes)
|
||||
{
|
||||
vfs_pipe_t *pipe = calloc(1, sizeof(vfs_pipe_t));
|
||||
pipe->buffer = malloc(size);
|
||||
pipe->size = size;
|
||||
|
||||
nodes[0] = calloc(1, sizeof(vfs_node_t));
|
||||
nodes[0]->d = &pipe_driver;
|
||||
nodes[0]->data = pipe;
|
||||
nodes[0]->flags = PIPE_READ;
|
||||
|
||||
nodes[1] = calloc(1, sizeof(vfs_node_t));
|
||||
nodes[1]->d = &pipe_driver;
|
||||
nodes[1]->data = pipe;
|
||||
nodes[1]->flags = PIPE_WRITE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
###Using pipes
|
||||
When starting up the keyboard driver, a pipe is created and connected to
|
||||
`stdin` - the first file descriptor - of the current process.
|
||||
|
||||
void keyboard_init()
|
||||
{
|
||||
INODE tmp[2];
|
||||
new_pipe(1024, tmp);
|
||||
|
||||
keyboard_pipe = tmp[1];
|
||||
vfs_open(keyboard_pipe, O_WRONLY);
|
||||
|
||||
process_t *p = current->proc;
|
||||
p->fd[0] = calloc(1, sizeof(file_desc_t));
|
||||
fd_get(p->fd[0]);
|
||||
p->fd[0]->ino = tmp[0];
|
||||
p->fd[0]->flags = O_RDONLY;
|
||||
vfs_open(tmp[0], O_RDONLY);
|
||||
|
||||
register_int_handler(IRQ2INT(IRQ_KBD), keyboard_handler);
|
||||
}
|
||||
|
||||
The keyboard handler (based off
|
||||
[Brandon Friesen's tutorial](http://www.osdever.net/bkerndev/Docs/keyboard.htm)
|
||||
writes each decoded key to the pipe:
|
||||
|
||||
registers_t *keyboard_handler(registers_t *r)
|
||||
{
|
||||
char code[2];
|
||||
|
||||
[...]
|
||||
|
||||
while(inb(KBD_STATUS_PORT) & 0x2);
|
||||
scancode = inb(KBD_DATA_PORT);
|
||||
code[0] = keyboard_decode(scancode);
|
||||
code[1] = '\0';
|
||||
if(code[0])
|
||||
{
|
||||
vfs_write(keyboard_pipe, (char *)code, 1, 0);
|
||||
}
|
||||
|
||||
[...]
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
a terminal process which in turn pipes the decoded keys on to the shell,
|
||||
and so on...
|
||||
|
||||
Next step is to add an actual filesystem! Of sorts...
|
224
pages/2014-01-27-TAR-filesystem.md
Normal file
@ -0,0 +1,224 @@
|
||||
layout: post
|
||||
title: "TAR Filesystem"
|
||||
subtitle: "Almost a useful system"
|
||||
tags: [osdev]
|
||||
|
||||
It's finally time to implement an actuall filesystem, and all the hard
|
||||
work with the VFS framework will pay off.
|
||||
|
||||
For my first filesystem, I chose the tape archive format - also known as
|
||||
a tarball. It's a bit like what
|
||||
[James Molloy](http://www.jamesmolloy.co.uk/tutorial_html/8.-The%20VFS%20and%20the%20initrd.html)
|
||||
did but with support for directories.
|
||||
|
||||
###Tar file format
|
||||
The tar file format - as the name implies - was designed for storing
|
||||
files on magnetic tape, which can only be read and written sequentially.
|
||||
Therefore, there is no index of the files in the archive, but each file
|
||||
is preceded by a data block in human-readable format.
|
||||
|
||||
In the POSIX standard of 1988, the data block has the following format:
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char name[100];
|
||||
unsigned char mode[8];
|
||||
unsigned char uid[8];
|
||||
unsigned char gid[8];
|
||||
unsigned char size[12];
|
||||
unsigned char mtime[12];
|
||||
unsigned char checksum[8];
|
||||
unsigned char type[1];
|
||||
unsigned char linkname[100];
|
||||
unsigned char tar_indicator[6];
|
||||
unsigned char tar_version[2];
|
||||
unsigned char owner[32];
|
||||
unsigned char group[32];
|
||||
unsigned char device_major[8];
|
||||
unsigned char device_minor[8];
|
||||
unsigned char prefix[155];
|
||||
}__attribute__((packed)) tar_header_t;
|
||||
|
||||
The data block is 500 bytes long, but the data starts 512 (one standard
|
||||
disk sector) bytes after the start of the data block
|
||||
The data itself is also padded so that the next data block starts at
|
||||
a 512 byte boundary.
|
||||
|
||||
For historical reasons all numerical values (i.e. `uid`, `gid`, `size`,
|
||||
`mtime`, `checksum`, `device_major` and `device_minor`) are in __octal__
|
||||
ascii format (ascii `'0'` to `'7'`) padded with leading zeroes and
|
||||
terminated with a `null` character (`0x00`) or space (`0x20`).
|
||||
|
||||
###Reshaping the tree
|
||||
The TAR format is very good for storing data on tape where read and
|
||||
write operations are sequential, but it makes less sense once the files
|
||||
are loaded into ram and you might want to access them randomly.
|
||||
|
||||
Instead, I reshape the file list into a file tree:
|
||||
|
||||
tree_t *build_tar_tree(tar_header_t *tar)
|
||||
{
|
||||
...
|
||||
while(tar->name[0])
|
||||
{
|
||||
tartree_add_node(tree, tar, (char *)&tar->name);
|
||||
uint32_t size;
|
||||
sscanf((char *)&tar->size, "%o", &size);
|
||||
tar = (tar_header_t *)((size_t)tar + size + 512);
|
||||
if((size_t)tar % 512)
|
||||
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%512))
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
|
||||
{
|
||||
...
|
||||
tree_node_t *node = tree->root;
|
||||
char *p;
|
||||
for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
|
||||
{
|
||||
int found = 0;
|
||||
list_t *l;
|
||||
for_each_in_list(&node->children)
|
||||
{
|
||||
...
|
||||
if(!strcmp(entry->name, p))
|
||||
{
|
||||
found = 1;
|
||||
node = tn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
{
|
||||
...
|
||||
tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
|
||||
n->name = strdup(p);
|
||||
n->tar = tar;
|
||||
...
|
||||
tree_make_child(node, new);
|
||||
node = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
strange things happen. Deterministic and expected things, mind you, but
|
||||
strange.
|
||||
|
||||
###Mounting the filesystem
|
||||
The tarfs driver makes use of the data field in the vfs node to store
|
||||
the tar tree.
|
||||
|
||||
INODE tarfs_init(tar_header_t *tar)
|
||||
{
|
||||
vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(node->name, "tarfs");
|
||||
node->d = &tarfs_driver;
|
||||
node->type = FS_DIRECTORY;
|
||||
|
||||
tree_t *tar_tree = build_tar_tree(tar);
|
||||
node->data = tar_tree->root;
|
||||
free(tar_tree);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
I then add the following to my kernel initialization function:
|
||||
|
||||
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start);
|
||||
vfs_mount("/", tarfs_init(tarfs_location));
|
||||
|
||||
where `mods` is the multiboot modules table as loaded by grub.
|
||||
|
||||
###The tarfs driver
|
||||
Finally, the tarfs driver functions. For now I only need to implement
|
||||
`read()` and `finddir()`.
|
||||
|
||||
INODE tar_finddir(INODE dir, const char *name)
|
||||
{
|
||||
tree_node_t *tn = (tree_node_t *)dir->data;
|
||||
list_t *l;
|
||||
for_each_in_list(&tn->children, l)
|
||||
{
|
||||
tree_node_t *cn = list_entry(l, tree_node_t, siblings);
|
||||
...
|
||||
if(!strcmp(entry->name, name)
|
||||
{
|
||||
INODE node = calloc(1, sizeof(vfs_node_t));
|
||||
strcpy(node->name, entry->name);
|
||||
node->d = &tarfs_driver;
|
||||
node->data = (void *)cn;
|
||||
sscanf((char *)&entry->tar->size, "%o", &node->length);
|
||||
if(entry->tar->type[0] == TAR_TYPE_DIR)
|
||||
node->type = FS_DIRECTORY;
|
||||
else
|
||||
node->type = FS_FILE;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
`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.
|
||||
This should be true for all `finddir` functions, but I think I've missed
|
||||
it at a few places... Someday I will clean up all my memory leaks.
|
||||
|
||||
Anyway, `finddir` also finds the right node in the tarfs tree and puts
|
||||
it in the `data` field of the inode.
|
||||
|
||||
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
||||
{
|
||||
tree_node_t *tn = (tree_node_t *)node->data;
|
||||
tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
|
||||
tar_header_t *tar = te->tar;
|
||||
|
||||
uint32_t tsz;
|
||||
sscanf((char *)&tar->size, "%o", &tsz);
|
||||
if(offset > tsz) return EOF;
|
||||
|
||||
if((size + offset) > tsz) size = tsz - offset;
|
||||
|
||||
offset = offset + (uint32_t)tar + 512;
|
||||
memcpy(buffer, (void *)offset, size);
|
||||
if(size == tsz - offset)
|
||||
((char *)buffer)[size] = EOF;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
###Using it
|
||||
Now, all I need to do in order to make read-only files accessible to my
|
||||
kernel is put them in a directory and run
|
||||
|
||||
$ tar -cf tarfs.tar tarfs/*
|
||||
|
||||
and then make sure `tarfs.tar` is loaded as a multiboot module by qemu
|
||||
|
||||
$ qemu-system-i386 -kernel kernel -initrd tarfs.tar
|
||||
|
||||
or by adding a line to the grub `menu.lst` file.
|
||||
|
||||
module /boot/tarfs.tar
|
||||
|
||||
###Final note
|
||||
While writing this post, I got back to polishing this code and added
|
||||
`readdir` for the tarfs. I also added buffering of inodes, so that if
|
||||
the same file is asked for twice, the same inode is returned. This meant
|
||||
I had to add a `flush` function to the driver structure.
|
||||
|
||||
I also rewrote a bit of both `vfs_finddir` and `vfs_readdir`. This in
|
||||
turn forced me to go back on a design decision. The VFS can now only
|
||||
mount new devices onto already existing paths (excluding `/`).
|
||||
|
||||
Just in case I'd write something in coming posts that might confuse
|
||||
you...
|
||||
|
||||
### Git
|
||||
I finally decided to push upstream, so here's the latest commit:
|
||||
[843520405e](https://github.com/thomasloven/os5/tree/843520405eb950413c75b2ca2a0ee638f15e7f4e).
|
||||
It still contains some stuff I haven't written about, though.
|
||||
|
265
pages/2014-01-28-Process-Syscalls.md
Normal file
@ -0,0 +1,265 @@
|
||||
layout: post
|
||||
title: "Process Syscalls"
|
||||
subtitle: "Almost caught up..."
|
||||
tags: [osdev]
|
||||
|
||||
I've described my [syscall interface](/blog/2013/06/System-Calls/) previously. I've also described
|
||||
the [file-related syscalls](/blog/2013/12/VFS-syscalls/). In order to build [newlib](http://wiki.osdev.org/Porting_Newlib), some more
|
||||
syscalls are required.
|
||||
|
||||
Those are:
|
||||
|
||||
void *sbrk(int incr);
|
||||
int getpid();
|
||||
int fork();
|
||||
void _exit(int rc);
|
||||
int wait(int *status);
|
||||
int kill(int pid, int sig);
|
||||
int execve(char *name, char **argv, char **env);
|
||||
|
||||
Let's just go through them one at a time:
|
||||
|
||||
###sbrk
|
||||
`sbrk` is a bit special, since it actually has two versions - one for
|
||||
kernel use and one for user space processes.
|
||||
|
||||
The user space one makes use of the [process memory
|
||||
manager](/blog/2013/06/Even-More-Memory/) to return
|
||||
a chunk of new memory for the `malloc` functions.
|
||||
|
||||
void *usr_sbrk(int incr)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
mem_area_t *area = find_including(p, p->mm.data_end);
|
||||
if(area)
|
||||
{
|
||||
if(area->end > (p->mm.data_end + incr))
|
||||
{
|
||||
// The current memory area is large enough
|
||||
} else {
|
||||
// Increase memory area
|
||||
new_area(p, area->end, p->mm.data_end + incr, \
|
||||
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
||||
MM_TYPE_DATA);
|
||||
}
|
||||
} else {
|
||||
// Create a new memory area
|
||||
new-area(p, p->mm.data_end, p->mm.data_end + incr, \
|
||||
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
||||
MM_TYPE_DATA);
|
||||
}
|
||||
p->mm.data_end = p->mm.data_end + incr;
|
||||
return (void *)(p->mm.data_end - incr);
|
||||
}
|
||||
|
||||
The kernel space version is just a simple linear allocator
|
||||
|
||||
uintptr_t kmem_top = KERNEL_HEAP_START;
|
||||
uintptr_t kmem_ptr = KERNEL_HEAP_START;
|
||||
void *sbrk(int incr)
|
||||
{
|
||||
if(kmem_ptr + incr > KERNEL_HEAP_END)
|
||||
{
|
||||
// PANIC!
|
||||
...
|
||||
}
|
||||
while(kmem_top < kmem_ptr + incr)
|
||||
{
|
||||
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
|
||||
PAGE_PRESENT | PAGE_WRITE));
|
||||
kmem_top += PAGE_SIZE;
|
||||
}
|
||||
kmem_ptr = kmem_ptr + incr;
|
||||
return (void *)kmem_ptr - incr;
|
||||
}
|
||||
|
||||
Hopefully it's obvious why the kernel one is called `sbrk` while the
|
||||
user one has a different name.
|
||||
|
||||
###getpid
|
||||
`getpid` is rather obvious:
|
||||
|
||||
int getpid()
|
||||
{
|
||||
return current->proc->pid;
|
||||
}
|
||||
|
||||
###fork
|
||||
`fork` clones the current process and starts a new thread of execution.
|
||||
|
||||
int fork()
|
||||
{
|
||||
process_t *child = fork_process();
|
||||
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
|
||||
ch_thread->r.eax = 0;
|
||||
scheduler_insert(ch_thread);
|
||||
return child->pid;
|
||||
}
|
||||
|
||||
###_exit
|
||||
`_exit` stops a program and wakes up any processes that are sleeping on
|
||||
it.
|
||||
|
||||
void _exit(int rc)
|
||||
{
|
||||
process_t *p = current->proc;
|
||||
|
||||
// Close all open files
|
||||
int i;
|
||||
for(i = 0; i < NUM_FILEDES; i++)
|
||||
{
|
||||
if(p->fd[i])
|
||||
close(i);
|
||||
}
|
||||
exit_process(current->proc, rc);
|
||||
current->state = THREAD_STATE_FINISHED;
|
||||
schedule();
|
||||
}
|
||||
|
||||
`_exit` doesn't return, and in fact `schedule()` will never return as
|
||||
far as this thread is concerned.
|
||||
Note that the process still exists. It is not completely destroyed until
|
||||
its parent process has executed a `wait` syscall.
|
||||
|
||||
###wait
|
||||
Actually, I didn't quite implement `wait` yet, but instead use
|
||||
a `waitpid` for now, which is a bit more specific:
|
||||
|
||||
int waitpid(int pid)
|
||||
{
|
||||
process_t *proc = get_process(pid);
|
||||
while(proc->state != PROC_STATE_FINISHED)
|
||||
{
|
||||
scheduler_sleep(current, &proc->waiting);
|
||||
schedule();
|
||||
}
|
||||
int ret = proc->exit_code;
|
||||
free_process(proc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
This _should_ contain a check that process `pid` is a child of the
|
||||
current process too...
|
||||
|
||||
###kill
|
||||
I'll let `kill` wait for now. My next post will probably be on signals,
|
||||
so it'll fit better there anyway.
|
||||
|
||||
###execve
|
||||
Now, here's the big stuff.
|
||||
|
||||
`execve` launches new programs from the filesystem, so what it has to do
|
||||
is:
|
||||
|
||||
- Find the correct executable
|
||||
- Save the arguments
|
||||
- Save the environmental variables
|
||||
- Free the user memory space
|
||||
- Load the executable
|
||||
- Prepare a new user stack
|
||||
- Restore the arguments and environment variables
|
||||
|
||||
First of all, the executable is found. If it doesn't exist, we want to
|
||||
fail as early as possible - before we destroy everything.
|
||||
|
||||
int execve(char *name, char **argv, char **env)
|
||||
{
|
||||
INODE executable = vfs_namei(name);
|
||||
if(!executable)
|
||||
{
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
...
|
||||
|
||||
The arguments and environment are null-terminated lists of strings
|
||||
stored in user space, so they have to be copied into kernel space before
|
||||
the user space is destroyed:
|
||||
|
||||
...
|
||||
usigned int envc = 0;
|
||||
char **temp_env = 0;
|
||||
if(env)
|
||||
{
|
||||
while(env[envc++]); // Count number of environmental variables
|
||||
|
||||
temp_env = calloc(envc, sizeof(char *));
|
||||
unsigned int i = 0;
|
||||
while(env[i])
|
||||
{
|
||||
temp_env[i] = strdup(env[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the same thing for argv
|
||||
...
|
||||
|
||||
Next, Delete all memory from the previous executable and [load the new
|
||||
one](/blog/2013/08/Loading-Elf/):
|
||||
|
||||
procmm_removeall(current->proc);
|
||||
load_elf(executable);
|
||||
current->r.eax = current->r.ebx = current->r.ecx = \
|
||||
current->r.edx = 0;
|
||||
|
||||
We need to put the arguments and environment back into the new
|
||||
executable's user space, so a new stack area is created:
|
||||
|
||||
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
|
||||
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
|
||||
MM_TYPE_STACK);
|
||||
current->kernel_thread = (registers_t *)current;
|
||||
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
|
||||
|
||||
Then, copy the environment and arguments onto the stack:
|
||||
|
||||
if(env)
|
||||
{
|
||||
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
|
||||
env = (char **)pos;
|
||||
int i = 0;
|
||||
while(temp_env[i])
|
||||
{
|
||||
pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
|
||||
memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
|
||||
env[i] = (char *)pos;
|
||||
i++;
|
||||
}
|
||||
env[envc-1] = 0;
|
||||
}
|
||||
// Do the same for argc
|
||||
...
|
||||
|
||||
And finally, push the argument count, argument list and environment list
|
||||
onto the stack:
|
||||
|
||||
pos = pos - 3;
|
||||
pos[0] = (uint32_t)argc - 1;
|
||||
pos[1] = (uint32_t)argv;
|
||||
pos[2] = (uint32_t)env;
|
||||
|
||||
current->r.useresp = current->r.ebp = (uint32_t)pos;
|
||||
current->r.ecx = (uint32_t)pos;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
This pushes argc, argv and env as arguments to the executabl. We can
|
||||
use this to set up the `environ` variable of newlib. The crt0 in newlib
|
||||
pushes `ecx` to the stack and then calls `_init` which looks like this:
|
||||
|
||||
extern char **environ;
|
||||
void _init(uint32_t *args)
|
||||
{
|
||||
int argc;
|
||||
char **argv;
|
||||
if(args)
|
||||
{
|
||||
argc = args[0];
|
||||
argv = (char **)args[1];
|
||||
environ = (char **)args[2];
|
||||
} else {...}
|
||||
|
||||
exit(main(argc, argv));
|
||||
}
|
180
pages/2014-01-30-Signals.md
Normal file
@ -0,0 +1,180 @@
|
||||
layout: post
|
||||
title: "Signals"
|
||||
subtitle: "Another rewrite"
|
||||
tags: [osdev]
|
||||
|
||||
Time for another live rewrite - or at least a restructuring.
|
||||
|
||||
###Signals in newlib
|
||||
|
||||
Newlib has a kind of signals built in without any help from the kernel.
|
||||
Those signals can't be sent between processes, though, so I'd like to
|
||||
implement my own.
|
||||
|
||||
I'll just look at `signal` and `kill` and ignore things like
|
||||
`sigaction` for now.
|
||||
|
||||
First of all, in order to compile newlib with kernel supported signals,
|
||||
you need the line
|
||||
|
||||
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
|
||||
|
||||
in your entry in `newlib/configure.host` as described in [the osdev
|
||||
wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
|
||||
|
||||
Then you need the syscalls:
|
||||
|
||||
sig_t signal(int signum, sig_t handler);
|
||||
int kill(int pid, int sig);
|
||||
|
||||
###Raising signals
|
||||
|
||||
The [posix
|
||||
specification](http://pubs.opengroup.org/onlinepubs/9699919799/)
|
||||
contains a lot of rules about how signals should behave in a number of
|
||||
situations. I probably got a lot of them wrong, but I'll try to fix them
|
||||
as I go along later.
|
||||
|
||||
The way I chose to implement signals was through a list for each
|
||||
process. When a signal is sent using `kill` or similar, a `signal_t`
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t sig;
|
||||
uint32_t sender;
|
||||
list_head_t queue;
|
||||
} signal_t;
|
||||
|
||||
is created and added to the processes list:
|
||||
|
||||
int signal_process(int pid, int signum)
|
||||
{
|
||||
process_t *p = get_process(pid);
|
||||
...
|
||||
signal_t *signal = calloc(1, sizeof(signal_t));
|
||||
signal->sig = signum;
|
||||
signal->sender = current->proc->pid;
|
||||
init_list(signal->queue);
|
||||
append_to_list(p->signal_queue, signal->queue);
|
||||
|
||||
if(p == current->proc)
|
||||
{
|
||||
handle_signals(current);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
If the currently running thread is in the process being signalled, we
|
||||
handle the signals immediately. Otherwise it can wait for a bit.
|
||||
|
||||
`signal_process` is called by the `kill` syscall.
|
||||
|
||||
###The signal syscall
|
||||
|
||||
The `signal` syscall lets the process select how to handle a certain
|
||||
signal. Each process also contains a table of `sig_t` and the `signal`
|
||||
syscall calls the following function:
|
||||
|
||||
sig_t switch_handler(int signum, sig_t handler)
|
||||
{
|
||||
...
|
||||
sig_t old = current->proc->signal_handler[signum];
|
||||
current->proc->signal_handler[signum] = handler;
|
||||
return old;
|
||||
}
|
||||
|
||||
The cut out part of this function contains code to make sure the handler
|
||||
of signal 9 (`SIGKILL`) is never changed from `SIG_DFL`.
|
||||
|
||||
###Handling signals
|
||||
|
||||
Signals should be handled as soon as possible, though there's no reason
|
||||
to let the receiving process skip the line in the scheduler. So as soon
|
||||
as the process continues running should be soon enough.
|
||||
|
||||
I chose to hook into the my interrupt handler:
|
||||
|
||||
registers_t *idt_handler(registers_t *r)
|
||||
{
|
||||
...
|
||||
if(int_handlers[r->int_no)
|
||||
{
|
||||
...
|
||||
registers_t *ret = int_handlers[r->int_no](r);
|
||||
|
||||
if((ret->cs & 0x3) == 0x3)
|
||||
{
|
||||
ret = (registers_t *)handle_signals((thread_t *)ret);
|
||||
...
|
||||
}
|
||||
...
|
||||
return ret
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
So, what does `handle_signals` actually do?
|
||||
|
||||
It start by finding the signal queue of the process to which the passed
|
||||
thread belongs and steps through it one entry at a time. If a signal is
|
||||
queued and not blocked (actual blocking is not implemented yet) it
|
||||
checks what the handler for that signal number is. The handler may be
|
||||
`SIG_IGN`, `SIG_DFL` or a pointer to a function in userspace.
|
||||
|
||||
If the handler is set to `SIG_DFL` the function looks up the correct
|
||||
default signal handler in a table and calls it. It may be one of the
|
||||
following:
|
||||
|
||||
void sighandler_ignore(int num)
|
||||
{
|
||||
(void)num;
|
||||
}
|
||||
void sighandler_terminate(int num)
|
||||
{
|
||||
fprintf(stderr,, "Process %x terminated by signal %x\n", \
|
||||
current->proc->pid, num);
|
||||
_exit(num);
|
||||
}
|
||||
void sighandler_coredump(int num)
|
||||
{ /* Same as above */ }
|
||||
void sighandler_stop(int num)
|
||||
{ /* Not implemented yet */ }
|
||||
void sighandler_continue(int num)
|
||||
{ /* Not implemented yet */ }
|
||||
|
||||
If the handler is a function a new thread is created to handle it:
|
||||
|
||||
...
|
||||
sig_t handler = th->proc->signal_handler[signal->sig];
|
||||
thread_t *h = new_thread((void (*)(void))handler, 1);
|
||||
|
||||
append_to_list(th->proc->threads, h->process_threads);
|
||||
h->proc = th->proc;
|
||||
uint32_t *stack = ((uint32_t *)th->r.useresp;
|
||||
*--stack = signal->sig;
|
||||
*--stack = SIGNAL_RETURN_ADDRESS;
|
||||
h->r.useresp = h->r.ebp = (uint32_t)stack;
|
||||
remove_from_list(signal->queue);
|
||||
free(signal);
|
||||
|
||||
scheduler_remove(h);
|
||||
scheduler_sleep(th, &h->waiting);
|
||||
scheduler_cheat(h);
|
||||
schedule();
|
||||
}
|
||||
|
||||
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
|
||||
at the beginning of the schedulers queue and switches to it.
|
||||
|
||||
`SIGNAL_RETURN_ADDRESS` is chosen to cause a page fault when the signal
|
||||
handler returns. The page fault handler catches this and recognizes the
|
||||
address so that the thread can be safely destroyed.
|
||||
|
||||
Now the page fault handler can also be setup to send a `SIGSEGV` signal
|
||||
when a userspace thread page faults.
|
||||
|
||||
###Code
|
||||
|
||||
Git commit
|
||||
[1ec132c](https://github.com/thomasloven/os5/tree/1ec132ce60ba627e522019d456e0cec993193153)
|
116
pages/2014-01-31-Debug-Printing.md
Normal file
@ -0,0 +1,116 @@
|
||||
layout: post
|
||||
title: "Debug Printing"
|
||||
subtitle: "Running out of space"
|
||||
tags: [osdev]
|
||||
|
||||
I still haven't quite caught up with all the stuff I wanted to write
|
||||
about [back in August](/blog/2013/08/Catching-Up/), but at some point
|
||||
when I started to write little userspace programs that wrote stuff to
|
||||
the screen, I thought my kernel debug messages started to get in the
|
||||
way.
|
||||
|
||||
I'm still not ready to give up debug messages from the kernel, so the
|
||||
best alternative seemed to be to output them to something else, such as
|
||||
a serial port.
|
||||
|
||||
Writing to a serial port [is rather
|
||||
easy](http://wiki.osdev.org/Serial_Ports), so there's no need for me to
|
||||
go into detail there. I made a few changes to my emulation setup,
|
||||
though, that made showing the debug messages a bit nicer.
|
||||
|
||||
###Tmux
|
||||
|
||||
I've described how I use tmux with qemu, gdb and telnet
|
||||
[before](/blog/2013/02/New-Environment/). It was easy to add another
|
||||
pane which displays the debug information, but one thing at a time.
|
||||
Here's what I did in a bash script:
|
||||
|
||||
tmux split-window -h 'qemu-system-i386 -kernel build/kernel/kernel -initrd "build/tarfs.tar" -curses -monitor telnet:localhost:4444,server -s -S -serial file:serial.out'
|
||||
|
||||
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
|
||||
server for controlling the emulator. `-s` starts a gdb server at tcp
|
||||
port 1234 and `-S` makes the emulator wait for a command before it
|
||||
starts running the kernel. Finally, the `-serial` flag saves all output
|
||||
from serial port 1 to a file.
|
||||
|
||||
The next step is:
|
||||
|
||||
tmux split-window -v 'tail -f serial.out | util/colorize.sh'
|
||||
|
||||
which opens up another tmux pane that displays the serial output using
|
||||
the auto-updating feature of `tail`. The script `colorize.sh` colorizes
|
||||
certain words of the output:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
C_NO=`echo -e "\\033[0m"`
|
||||
C_RED=`echo -e "\\033[31m"`
|
||||
C_GREEN=`echo -e "\\033[32m"`
|
||||
C_YELLOW=`echo -e "\\033[33m"`
|
||||
C_BLUE=`echo -e "\\033[36m"`
|
||||
|
||||
while read line; do
|
||||
echo $line | sed \
|
||||
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
|
||||
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
|
||||
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
|
||||
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
|
||||
done
|
||||
|
||||
Next is
|
||||
|
||||
tmux select-pane -L
|
||||
tmux split-window -v 'i586-elf-gdb'
|
||||
tmux select-pane -U
|
||||
telnet localhost 4444
|
||||
|
||||
Which opens a new pane for the `gdb` debugger and then moves back to the
|
||||
first pane to open the telnet terminal.
|
||||
|
||||
[](/media/img/debug_print_full.png)
|
||||
|
||||
###Git
|
||||
|
||||
Notice the yellow lines in that screenshot?
|
||||
The ones that say
|
||||
|
||||
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
|
||||
(HEAD, harddrive): Ext2 unlink function. Untested.
|
||||
Kernel compilation: Mar 7 2014 14:06:27
|
||||
|
||||
The data for this is stored in a special file called `version.c`
|
||||
|
||||
#include <version.h>
|
||||
|
||||
char * __kernel_git_hash = GITHASH;
|
||||
char * __kernel_git_date = GITDATE;
|
||||
int __kernel_git_dirty = GITDIRTY;
|
||||
char * __kernel_git_message = GITMESSAGE;
|
||||
char * __kernel_git_branch = GITBRANCH;
|
||||
|
||||
char * __kernel_build_date = __DATE__;
|
||||
char * __kernel_build_time = __TIME__;
|
||||
|
||||
which has a special line in the kernel makefile:
|
||||
|
||||
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
|
||||
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
|
||||
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
|
||||
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \
|
||||
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")'
|
||||
|
||||
A few more lines makes sure `version.c` is always recompiled if any
|
||||
other part of the kernel is:
|
||||
|
||||
kernel: $(kernel_objects)
|
||||
rm -f version.o
|
||||
$(MAKE) version.o
|
||||
$(LINK)
|
||||
|
||||
Obviously, this is a bit simplified. But not much. I might make a post
|
||||
about my makefiles some day...
|
||||
|
||||
But with this, I've kind of caught up. Neat! Back to programming ahead
|
||||
of myself!
|
169
pages/2014-04-15-Dito-Framework.md
Normal file
@ -0,0 +1,169 @@
|
||||
layout: post
|
||||
title: "DITo - Framework"
|
||||
subtitle: "the Disk Image TOols"
|
||||
tags: [osdev]
|
||||
|
||||
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
|
||||
territory for me. In fact, my only experience from disks and filesystems
|
||||
were from when I got started in osdeving and found some tutorial in pdf
|
||||
form which described how to write a bootloader in asm that read a kernel
|
||||
from a FAT12 floppy disk.
|
||||
|
||||
Since then, whenever I needed a disk image for testing, I'd go through
|
||||
a painful process of finding an image with GRUB preinstalled, mounting
|
||||
it using a discontinued third party application, copy stuff to it, hope
|
||||
I would be able to unmount it without the entire computer freezing up
|
||||
and finally pray that it worked when I started the emulator. In short,
|
||||
trying to manage a disk image from the command line in OSX sucks.
|
||||
|
||||
That's when I realized I could kill two birds with one stone. By writing
|
||||
a tool for managing files in a disk image without mounting it, I could
|
||||
gain understanding and experience of working with filesystems. If I
|
||||
wrote it well, I would probably be able to reuse much of the code for
|
||||
my kernel as well. At the time I had just finished my master thesis and
|
||||
had all but signed the contract for my current employment, so I had some
|
||||
free time on my hands while the paperwork fell through.
|
||||
|
||||
The result was [DITo - Disk Image
|
||||
Tools](https://github.com/thomasloven/dito), a c library and set of
|
||||
applications for creating and handling disk images from the command
|
||||
line.
|
||||
|
||||
Recently, I actually did copy some of the code from DITo into my kernel.
|
||||
Immagine my surprise when it actually worked like a charm after changing
|
||||
only a few function calls.
|
||||
|
||||
I've since realized a couple of mistakes though, and decided to rewrite
|
||||
some parts from scratch. Let's go!
|
||||
|
||||
###Drive operations
|
||||
|
||||
The basic operations of DITo are reading from or writing to image files
|
||||
or disk drives. Each drive type has a driver
|
||||
|
||||
typedef struct drive_driver
|
||||
{
|
||||
int (*open)(struct drive_t *d, int flags);
|
||||
int (*close)(struct drive_t *d, int flags);
|
||||
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
||||
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
||||
} drive_driver_t;
|
||||
|
||||
The drive type contains a pointer to the driver and a pointer to some
|
||||
arbitrary data used by the driver.
|
||||
|
||||
typedef struct drive_t
|
||||
{
|
||||
struct drive_driver *d;
|
||||
void *data;
|
||||
} drive_t;
|
||||
|
||||
Then there are some wrapper functions for performing the required
|
||||
operations:
|
||||
|
||||
int drive_open(struct drive_t *d, int flags)
|
||||
{
|
||||
if(d->d->open)
|
||||
return d->d->open(d, flags);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
and simmilar for `drive_close`, `drive_read` and `drive_write`.
|
||||
|
||||
###Filesystem operations
|
||||
|
||||
The next important part of DITo is the filesystem handling. After
|
||||
thinking about it, the important primitive functions for all file
|
||||
operations I could think about are all in a filesystem driver struct:
|
||||
|
||||
struct fs_driver
|
||||
{
|
||||
INODE (*open)(struct fs_t *fs, const char *path, int flags);
|
||||
int (*close)(struct fs_t *fs, INODE ino);
|
||||
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
||||
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
||||
int (*truncate)(struct fs_t *fs, INODE ino, off_t length);
|
||||
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st);
|
||||
|
||||
int (*touch)(struct fs_t *fs, const char *path, struct stat *st);
|
||||
int (*link)(struct fs_t *fs, const char *path1, const char *path2);
|
||||
int (*unlink)(struct fs_t *fs, const char *path);
|
||||
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num);
|
||||
};
|
||||
|
||||
The `fs_t` type contains a pointer to the driver, a pointer to the drive
|
||||
and a general data pointer.
|
||||
|
||||
typedef struct fs_t
|
||||
{
|
||||
struct fs_driver *driver;
|
||||
drive_t *d;
|
||||
void *data;
|
||||
} fs_t;
|
||||
|
||||
The wrapper functions `fs_open`, `fs_close` and so on work the same way
|
||||
as the `drive_*` functions.
|
||||
|
||||
The `INODE` type is a pointer to a struct containing a pointer to the
|
||||
filesystem, a unique inode number and a pointer to arbitrary data.
|
||||
|
||||
struct ino_st
|
||||
{
|
||||
fs_t *fs;
|
||||
unsigned int ino;
|
||||
void *data;
|
||||
};
|
||||
|
||||
typedef struct ino_st * INODE;
|
||||
|
||||
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
|
||||
`ino->fs`. I decided to keep it this way to get the function interface
|
||||
consistant, and also for the possible sanity check `fs == ino->fs`.
|
||||
|
||||
The idea behind the framework is that the same functions should be
|
||||
usable for all kinds of filesystems on all kinds of drives. For example,
|
||||
if I have one image of an FAT floppy disk with a file I want copied to
|
||||
the ext2 formated second partition of a hard drive image, I could do
|
||||
someting like this:
|
||||
|
||||
drive_t *fat_disk = image_drive("floppy.img");
|
||||
drive_open(fat_disk, READ_FLAG);
|
||||
drive_t *ext2_disk = image_drive("harddrive.img");
|
||||
drive_open(ext2_disk, READ_WRITE_FLAG);
|
||||
drive_t *ext2_partition = mbr_drive(ext2_disk, 2);
|
||||
drive_open(ext2_partition, READ_WRITE_FLAG);
|
||||
|
||||
fs_t *fat = fat_fs(fat_disk);
|
||||
fs_t *ext2 = ext2_fs(ext2_partition);
|
||||
|
||||
INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
|
||||
struct st *st = malloc(sizeof(struct st));
|
||||
fs_struct(fat, source, st);
|
||||
|
||||
fs_touch(ext2, "/new/path", st);
|
||||
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
|
||||
|
||||
void *buffer = malloc(BUFER_SIZE);
|
||||
off_t offset = 0;
|
||||
off_t add = 0;
|
||||
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset))
|
||||
{
|
||||
fs_write(ext2, destination, buffer, BUFFER_SIZE, offset);
|
||||
offset += add;
|
||||
}
|
||||
|
||||
fs_close(destination);
|
||||
fs_close(source);
|
||||
|
||||
drive_close(ext2_partition);
|
||||
drive_close(fat_disk);
|
||||
|
||||
Which of couse will eventually become its own tool so that the actual
|
||||
work the end user has to do becomes:
|
||||
|
||||
$ dito-cp floppy.img:/path/to/file harddrive.img:2:/new/path
|
||||
|
||||
|
126
pages/2014-04-16-Dito-Drives.md
Normal file
@ -0,0 +1,126 @@
|
||||
layout: post
|
||||
title: "DITo - Drives"
|
||||
subtitle: "Exploring the MBR"
|
||||
tags: [osdev]
|
||||
|
||||
Let's write a few drive drivers.
|
||||
|
||||
### Pure image
|
||||
|
||||
The simplest image we'd have to handle is a plain image file, such as
|
||||
a floppy disk. There's only a single partition and a one-to-one mapping
|
||||
from the image to the drive.
|
||||
|
||||
First of all, let's think of what data we'd need to store. The filename
|
||||
might be nice to have, and probably a pointer to the opened file. Would
|
||||
hate to lose that...
|
||||
|
||||
struct im_data
|
||||
{
|
||||
char *filename;
|
||||
FILE *file;
|
||||
};
|
||||
|
||||
The functions required for the driver would then make use of the
|
||||
c standard library:
|
||||
|
||||
int im_open(drive_t *d, int flags)
|
||||
{
|
||||
struct im_data *data = d->data;
|
||||
data->file = fopen(data->filename, flags);
|
||||
return 1;
|
||||
}
|
||||
int im_close(drive_t *d, int flags)
|
||||
{
|
||||
struct im_data *data = d->data;
|
||||
fclose(data->file);
|
||||
return 1;
|
||||
}
|
||||
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
|
||||
{
|
||||
struct im_data *data = d->data;
|
||||
fseek(data->file, offset, SEEK_SET);
|
||||
return fread(buffer, length, 1, data->file);
|
||||
}
|
||||
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
|
||||
{
|
||||
struct im_data *data = d->data;
|
||||
fseek(data->file, offset, SEEK_SET);
|
||||
return fwrite(buffer, length, 1, data->file);
|
||||
}
|
||||
|
||||
The function for setting up the drive is equally simple:
|
||||
|
||||
drive_driver_t im_driver =
|
||||
{
|
||||
im_open,
|
||||
im_close,
|
||||
im_read,
|
||||
im_write,
|
||||
};
|
||||
|
||||
drive_t *image_drive(const char *filename)
|
||||
{
|
||||
drive_t *d = calloc(1, sizeof(drive_t));
|
||||
struct im_data *data = calloc(1, sizeof(struct im_data));
|
||||
|
||||
d->driver = &im_driver;
|
||||
d->data = data;
|
||||
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
As always, I've stripped away all sanity checking and error handling.
|
||||
|
||||
###MBR formated disks
|
||||
|
||||
In order to use a harddrive for different things (like file storage or
|
||||
swap space), it can be split into several partitions. One standard for
|
||||
doing this is through a table in the MBR or Master Boot Record of the
|
||||
disk.
|
||||
|
||||
The MBR starts with 436 bytes of space for a bootloader, then there's a
|
||||
10 byte disk ID followed by 4 partition table entries.
|
||||
|
||||
The entries of the table has the following structure:
|
||||
|
||||
struct MBR
|
||||
{
|
||||
uint8_t boot_indicator;
|
||||
uint8_t start_chs[3];
|
||||
uint8_t system_id;
|
||||
uint8_t end_chs[3];
|
||||
uint32_t start_lba;
|
||||
uint32_t num_sectors;
|
||||
}__attribute__((packed));
|
||||
|
||||
_CHS_ and _LBA_ are different ways of addressing sectors of the disk.
|
||||
|
||||
CHS - or Cylinder Sector Head - addresses the sectors by their physical
|
||||
location on the disk. The format in this table is first 8 bytes for
|
||||
the wanted head (two heads per harddrive platter), then 6 bits for the
|
||||
sector (angle on the platter) and finally 10 bits for the cylinder
|
||||
(distance from platter center). This method requires knowledge of the
|
||||
actual physical structure of the drive and maxes out at around 8 GB with
|
||||
512 byte sectors.
|
||||
|
||||
LBA - or Logical Block Addressing - leaves all that to the drive and
|
||||
addresses the sectors by a single number. With 512 byte sectors, this is
|
||||
useful up to about 2 GB.
|
||||
|
||||
For our purposes, the fields `start_lba` and `num_sectors` are all we
|
||||
need (and `system_id` to check if the partition table entry is used).
|
||||
|
||||
###MBR driver
|
||||
|
||||
The driver for reading from a partition is pretty alike the plain image
|
||||
driver. Principally, the only difference is that 512 times `start_lba`
|
||||
has to be added to the offset for reading and writing, and that care
|
||||
must be taken not to allow reading or writing outside of the partition
|
||||
boundaries.
|
||||
|
||||
I even wrote the driver to piggyback on a different driver. I.e. the
|
||||
function for creating the drive object requires a drive object as
|
||||
argument (as in the [example in the previous
|
||||
post](/blog/2014/04/Dito-Framework/)).
|
8
pages/2014-04-17-Dito-Ext2.md
Normal file
@ -0,0 +1,8 @@
|
||||
layout: post
|
||||
title: "DITo - Ext2"
|
||||
subtitle: "A beginning..."
|
||||
tags: [osdev]
|
||||
|
||||
It's finally time to start looking at an actual filesystem.
|
||||
|
||||
I chose to begin with ext2.
|
13
pages/about-page.md
Normal file
@ -0,0 +1,13 @@
|
||||
title: About this page
|
||||
permalink: /about/this-page/
|
||||
|
||||
I'm not really a ruby kind of guy, but this page runs on
|
||||
[Jekyll](http://jekyllrb.com/) and [Sass.](http://sass-lang.com/) anyway.
|
||||
|
||||
The color scheme is based on
|
||||
[Solarized](http://ethanschoonover.com/solarized) and the font is
|
||||
[DejaVu Serif](http://dejavu-fonts.org).
|
||||
|
||||
Code snippets are displayed using
|
||||
[google-code-prettify](http://code.google.com/p/google-code-prettify/) with
|
||||
some custom syntax files by me.
|
39
pages/about.md
Normal file
@ -0,0 +1,39 @@
|
||||
title: About me
|
||||
permalink: /about/
|
||||
|
||||
Thomas Lovén
|
||||
------------
|
||||
<img src="/media/img/thomas.png" width="300" class="right">
|
||||
|
||||
Born in Karlskrona in the south of Sweden. Lived there for 18 years
|
||||
before doing army service in Eksjö and then moving to Gothenburg for
|
||||
studying.
|
||||
|
||||
I'm currently 26, newly wed and back in Karlskrona.
|
||||
|
||||
On my free time I play clarinet or tinker with some electronics or
|
||||
programming project.
|
||||
|
||||
Education
|
||||
---------
|
||||
I have a masters degree in engineering physics with a Complex Adaptive
|
||||
Systems specialisation from Chalmers University of Technology in
|
||||
Gothenburg.
|
||||
|
||||
[Complex Adaptive Systems](http://studycas.com) focuses on describing
|
||||
and analyzing complex or stochastic systems as well as adaptive
|
||||
problem solving methods. Some courses I have read are Neural Networks,
|
||||
Biologically Inspired Optimization Algorithms, Dynamical Systems and
|
||||
Computational Biology.
|
||||
|
||||
[Further details](/about/education/)
|
||||
|
||||
Résumé
|
||||
------
|
||||
|
||||
Outdated. Will return after update.
|
||||
|
||||
About the page
|
||||
--------------
|
||||
[Further details](/about/this-page/)
|
||||
|
79
pages/education.md
Normal file
@ -0,0 +1,79 @@
|
||||
title: About me - Education details
|
||||
permalink: /about/education/
|
||||
|
||||
Teknisk fysik
|
||||
-------------
|
||||
|
||||
Teknisk fysik - Engineering physics - is a bachelors programme at Chalmers University of Technology.
|
||||
Courses are held in Swedish.
|
||||
Translation and grades can be provided on request.
|
||||
|
||||
I earned my bachelors degree in 2012.
|
||||
|
||||
TDA305 Datorintroduktion 1,5hp
|
||||
ESS115 Elektriska nät och system 7,5hp
|
||||
EEF031 Elektromagnetisk fältteori 7,5hp
|
||||
TIF080 Experimentell fysik 1 - mätteknik 9hp
|
||||
TIF090 Experimentell fysik 2 - bas 4,5hp
|
||||
FFY011 Fasta tillståndets fysik 7,5hp
|
||||
MVE035 Flervariabelanalys 6hp
|
||||
MVE030 Fourieranalys 6hp
|
||||
TIF010 Fysiken omkring oss 7,5hp
|
||||
TMA970 Inledande matematisk analys 6hp
|
||||
FKA150 Inledande teknisk kommunikation 1,5hp
|
||||
TIFX02 Kandidatarbete vid Teknisk fysik 15hp
|
||||
MVE025 Komplex matematisk analys 6hp
|
||||
FUF040 Kvantfysik 6hp
|
||||
TMA660 Linjär algebra och geometri 4,5hp
|
||||
TMA671 Linjär algebra och numerisk analys 7,5hp
|
||||
TMA976 Matematisk analys, fortsättning 6hp
|
||||
TMA321 Matematisk statistik 4,5hp
|
||||
FFM515 Mekanik 1 7,5hp
|
||||
FFM520 Mekanik 2 6hp
|
||||
TIF075 Miljöfysik 4,5hp
|
||||
FFY091 Optik 6hp
|
||||
TIN211 Programmeringsteknik 6hp
|
||||
ERE091 Reglerteknik 4,5hp
|
||||
FUF045 Speciell relativitetsteori 4,5hp
|
||||
TME055 Strömningsmekanik 4,5hp
|
||||
FUF050 Subatomär fysik 6hp
|
||||
FTF140 Termodynamik och statistisk fysik 7,5hp
|
||||
TIF100 Tillämpad kvantfysik 4,5hp
|
||||
FFM232 Vektorfält och klassisk fysik 4,5hp
|
||||
{: .prettyprint .lang-betyg}
|
||||
|
||||
###Bachelors thesis
|
||||
For our bachelors thesis me, two of my fellow students at Engineering
|
||||
physics and three students at Elektroteknik - roughly electrics and
|
||||
electronics - developed, built and tested a wireless charing station for
|
||||
cellular phones.
|
||||
|
||||
Complex Adaptive Systems
|
||||
------------------------
|
||||
|
||||
Complex Adaptive Systems is a masters programme at Chalmers University of Technology.
|
||||
Courses are held in English
|
||||
Grades can be provided on request.
|
||||
|
||||
As of January 2013 I have just begun work on my Masters thesis.
|
||||
|
||||
TIN092 Algorithms 7,5hp
|
||||
FFR135 Artificial neural networks 7,5hp
|
||||
FFR125 Autonomous agents 7,5hp
|
||||
FFR141 Complex systems seminar 7,5hp
|
||||
FFR110 Computational biology 1 7,5hp
|
||||
TDA351 Cryptography 7,5hp
|
||||
TIF115 Dynamical systems 7,5hp
|
||||
TIF160 Humanoid robotics 7,5hp
|
||||
RRY025 Image processing 7,5hp
|
||||
MVE370 Matematik och samhälle 7,5hp
|
||||
FFR120 Simulation of complex systems 7,5hp
|
||||
FFR105 Stochastic optimization algorithms 7,5hp
|
||||
MVE080 Vetenskaplig visualisering 7,5hp
|
||||
{: .prettyprint .lang-betyg}
|
||||
|
||||
__Matematik och samhälle__ translates to __Mathematics and society__.
|
||||
|
||||
__Vetenskaplig visualisering__ translate to __Scientific visualization__.
|
||||
|
||||
|
14
requirements.txt
Normal file
@ -0,0 +1,14 @@
|
||||
arrow==0.8.0
|
||||
click==6.6
|
||||
Flask==0.11.1
|
||||
Flask-FlatPages==0.6
|
||||
Frozen-Flask==0.13
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.8
|
||||
Markdown==2.6.7
|
||||
MarkupSafe==0.23
|
||||
Pygments==2.1.3
|
||||
python-dateutil==2.5.3
|
||||
PyYAML==3.12
|
||||
six==1.10.0
|
||||
Werkzeug==0.11.11
|
75
sitebuilder.py
Executable file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
from os.path import basename
|
||||
from glob import glob
|
||||
|
||||
from flask import Flask, render_template, send_from_directory
|
||||
from flask_flatpages import FlatPages
|
||||
from flask_frozen import Freezer
|
||||
import arrow
|
||||
|
||||
|
||||
DEBUG = True
|
||||
FLATPAGES_AUTO_RELOAD = DEBUG
|
||||
FLATPAGES_EXTENSION = '.md'
|
||||
FLATPAGES_ROOT = 'pages'
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
pages = FlatPages(app)
|
||||
freezer = Freezer(app)
|
||||
|
||||
permalinks = {}
|
||||
blogposts = []
|
||||
tags = set()
|
||||
for page in pages:
|
||||
parts = page.path.split('-')
|
||||
if page.meta.get('permalink', None):
|
||||
page.meta['url'] = page.meta['permalink']
|
||||
else:
|
||||
page.meta['url'] = '/blog/{}/{}/{}/'.format(parts[0], parts[1], '-'.join(parts[3:]))
|
||||
date = arrow.get('-'.join(parts[0:3]))
|
||||
page.meta['date'] = date
|
||||
page.meta['datestr'] = date.format('D MMM YYYY')
|
||||
blogposts.append(page)
|
||||
tags.update(page.meta.get('tags', []))
|
||||
permalinks[page.meta['url']] = page
|
||||
blogposts = sorted(blogposts, key=lambda p: p.meta['date'], reverse=True)
|
||||
|
||||
# Static files
|
||||
@app.route("/src/<path:path>")
|
||||
def scripts(path):
|
||||
return send_from_directory('src', path)
|
||||
@app.route("/media/<path:path>")
|
||||
def media(path):
|
||||
return send_from_directory('media', path)
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory('src', 'favicon.ico')
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def permalink(path):
|
||||
page = permalinks['/'+path]
|
||||
if path.startswith('blog/'):
|
||||
return render_template('post.html', page=page)
|
||||
return render_template('page.html', page=page)
|
||||
|
||||
@app.route("/blog/")
|
||||
def blog_default():
|
||||
return render_template('blog.html', posts=blogposts, tags=tags)
|
||||
|
||||
@app.route("/blog/<string:tag>/")
|
||||
def tag(tag):
|
||||
posts = [post for post in blogposts if tag in post.meta.get('tags', [])]
|
||||
return render_template('blog.html', posts=posts, title=tag, tags=tags)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "build":
|
||||
freezer.freeze()
|
||||
else:
|
||||
app.run()
|
BIN
src/Averia-Regular.ttf
Normal file
4
src/averia-cufon.all-nokern.js
Normal file
19
src/blankwin.js
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
this.blankwin = function(){
|
||||
var hostname = window.location.hostname;
|
||||
hostname = hostname.replace("www","").toLowerCase();
|
||||
var a = document.getElementsByTagName("a");
|
||||
this.check = function(obj){
|
||||
var href = obj.href.toLowerCase();
|
||||
return ((href.indexOf("http://")!=-1 || href.indexOf("https://")!=-1) && href.indexOf(hostname)==-1) ? true : false;
|
||||
};
|
||||
this.set = function(obj){
|
||||
obj.target = "_blank";
|
||||
obj.className = "external";
|
||||
};
|
||||
for (var i=0; i<a.length; i++) {
|
||||
if(check(a[i])) set(a[i]);
|
||||
};
|
||||
};
|
||||
|
||||
|
96
src/codestyle.css
Normal file
@ -0,0 +1,96 @@
|
||||
/* Text */
|
||||
.codehilite .t { color: #586e75 }
|
||||
|
||||
/* Whitespace */
|
||||
.codehilite .w { color: #073642 }
|
||||
|
||||
/* Error */
|
||||
.codehilite .err { color: #cb4b16; }
|
||||
|
||||
/* Keyword */
|
||||
.codehilite .k { color: #859900 }
|
||||
.codehilite .kc { color: #2aa198 } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #268bd2 } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #b58900 } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #859900 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #073642 } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #b58900 } /* Keyword.Type */
|
||||
|
||||
/* Name */
|
||||
.codehilite .n { color: #586e75 }
|
||||
.codehilite .na { color: #2aa198 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #268bd2 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #268bd2 } /* Name.Class */
|
||||
.codehilite .ne { color: #cb4b16 } /* Name.Error */
|
||||
.codehilite .no { color: #2aa198 } /* Name.Constant */
|
||||
.codehilite .nd { color: #2aa198 } /* Name.Decorator */
|
||||
.codehilite .ni { color: #2aa198; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .nf { color: #268bd2 } /* Name.Function */
|
||||
.codehilite .nn { color: #586e75; } /* Name.Namespace */
|
||||
.codehilite .nt { color: #2aa198; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #cb4b16 } /* Name.Variable */
|
||||
|
||||
/* Builtin */
|
||||
.codehilite .b { color: #859900 }
|
||||
.codehilite .bp { color: #586e75 } /* Name.Builtin.Pseudo */
|
||||
|
||||
/* Variable */
|
||||
.codehilite .v { color: #586e75 }
|
||||
.codehilite .vc { color: #586e75 } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #268bd2 } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #268bd2 } /* Name.Variable.Instance */
|
||||
|
||||
/* Literal */
|
||||
|
||||
/* Literal.Number */
|
||||
.codehilite .m { color: #268bd2 } /* Literal.Number */
|
||||
.codehilite .mf { color: #268bd2 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #268bd2 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #268bd2 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #268bd2 } /* Literal.Number.Oct */
|
||||
|
||||
/* Literal.String */
|
||||
.codehilite .s { color: #2aa198 }
|
||||
.codehilite .sb { color: #2aa198 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #2aa198 } /* Literal.String.Char */
|
||||
.codehilite .sd { color: #2aa198 } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #2aa198 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #cb4b16 } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #2aa198 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #cb4b16 } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #2aa198 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #cb4b16 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #2aa198 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #cb4b16 } /* Literal.String.Symbol */
|
||||
|
||||
/* Literal.Integer */
|
||||
.codehilite .il { color: #268bd2 } /* Literal.Number.Integer.Long */
|
||||
|
||||
/* Operator */
|
||||
.codehilite .o { color: #586e75 }
|
||||
.codehilite .ow { color: #859900 } /* Operator.Word */
|
||||
|
||||
/* Punctuation */
|
||||
.codehilite .p { color: #586e75 }
|
||||
|
||||
/* Comment */
|
||||
.codehilite .c { color: #93a1a1; font-style: italic }
|
||||
.codehilite .cm { color: #93a1a1; } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #93a1a1 } /* Comment.Preproc */
|
||||
.codehilite .c1 { color: #93a1a1; } /* Comment.Single */
|
||||
.codehilite .cs { color: #93a1a1; } /* Comment.Special */
|
||||
|
||||
.codehilite .hll { background-color: #dc322f }
|
||||
|
||||
/* Generic */
|
||||
.codehilite .g { color: #586e75 }
|
||||
.codehilite .gd { color: #586e75 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .gr { color: #586e75 } /* Generic.Error */
|
||||
.codehilite .gh { color: #586e75; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #586e75 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #586e75 } /* Generic.Output */
|
||||
.codehilite .gp { color: #586e75 } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: 586e75 } /* Generic.Strong */
|
||||
.codehilite .gu { color: #586e75; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #586e75 } /* Generic.Traceback */
|
1498
src/cufon-yui.js
Normal file
7
src/cufon-yui2.js
Normal file
34
src/desert.css
Normal file
@ -0,0 +1,34 @@
|
||||
* desert scheme ported from vim to google prettify */
|
||||
pre { display: block; background-color: #333 }
|
||||
pre .nocode { background-color: none; color: #000 }
|
||||
pre .str { color: #ffa0a0 } /* string - pink */
|
||||
pre .kwd { color: #f0e68c; font-weight: bold }
|
||||
pre .com { color: #87ceeb } /* comment - skyblue */
|
||||
pre .typ { color: #98fb98 } /* type - lightgreen */
|
||||
pre .lit { color: #cd5c5c } /* literal - darkred */
|
||||
pre .pun { color: #fff } /* punctuation */
|
||||
pre .pln { color: #fff } /* plaintext */
|
||||
pre .tag { color: #f0e68c; font-weight: bold } /* html/xml tag - lightyellow */
|
||||
pre .atn { color: #bdb76b; font-weight: bold } /* attribute name - khaki */
|
||||
pre .atv { color: #ffa0a0 } /* attribute value - pink */
|
||||
pre .dec { color: #98fb98 } /* decimal - lightgreen */
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE } /* IE indents via margin-left */
|
||||
li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
|
||||
/* Alternate shading for lines */
|
||||
li.L1,li.L3,li.L5,li.L7,li.L9 { }
|
||||
|
||||
@media print {
|
||||
pre { background-color: none }
|
||||
pre .str, code .str { color: #060 }
|
||||
pre .kwd, code .kwd { color: #006; font-weight: bold }
|
||||
pre .com, code .com { color: #600; font-style: italic }
|
||||
pre .typ, code .typ { color: #404; font-weight: bold }
|
||||
pre .lit, code .lit { color: #044 }
|
||||
pre .pun, code .pun { color: #440 }
|
||||
pre .pln, code .pln { color: #000 }
|
||||
pre .tag, code .tag { color: #006; font-weight: bold }
|
||||
pre .atn, code .atn { color: #404 }
|
||||
pre .atv, code .atv { color: #060 }
|
||||
}
|
BIN
src/favicon.ico
Normal file
After Width: | Height: | Size: 4.1 KiB |
364
src/fonts/dejavuserif/DejaVuSerif-demo.html
Executable file
@ -0,0 +1,364 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="specimen_files/easytabs.js" type="text/javascript" charset="utf-8"></script>
|
||||
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8" />
|
||||
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />
|
||||
|
||||
<style type="text/css">
|
||||
body{
|
||||
font-family: 'dejavu_serifbook';
|
||||
}
|
||||
</style>
|
||||
|
||||
<title>DejaVu Serif Book Specimen</title>
|
||||
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(document).ready(function() {
|
||||
$('#container').easyTabs({defaultContent:1});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
DejaVu Serif Book </div>
|
||||
<ul class="tabs">
|
||||
<li><a href="#specimen">Specimen</a></li>
|
||||
<li><a href="#layout">Sample Layout</a></li>
|
||||
<li><a href="#installing">Installing Webfonts</a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div id="main_content">
|
||||
|
||||
|
||||
<div id="specimen">
|
||||
|
||||
<div class="section">
|
||||
<div class="grid12 firstcol">
|
||||
<div class="huge">AaBb</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="glyph_range">A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="grid12 firstcol">
|
||||
<table class="sample_table">
|
||||
<tr><td>10</td><td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>11</td><td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>12</td><td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>13</td><td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>14</td><td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>16</td><td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>18</td><td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>20</td><td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>24</td><td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>30</td><td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>36</td><td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>48</td><td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>60</td><td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>72</td><td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
<tr><td>90</td><td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="section" id="bodycomparison">
|
||||
|
||||
|
||||
<div id="xheight">
|
||||
<div class="fontbody">◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body</div><div class="arialbody">body</div><div class="verdanabody">body</div><div class="georgiabody">body</div></div>
|
||||
<div class="fontbody" style="z-index:1">
|
||||
body<span>DejaVu Serif Book</span>
|
||||
</div>
|
||||
<div class="arialbody" style="z-index:1">
|
||||
body<span>Arial</span>
|
||||
</div>
|
||||
<div class="verdanabody" style="z-index:1">
|
||||
body<span>Verdana</span>
|
||||
</div>
|
||||
<div class="georgiabody" style="z-index:1">
|
||||
body<span>Georgia</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section psample psample_row1" id="">
|
||||
|
||||
<div class="grid2 firstcol">
|
||||
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid3">
|
||||
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid3">
|
||||
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid4">
|
||||
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="white_blend"></div>
|
||||
|
||||
</div>
|
||||
<div class="section psample psample_row2" id="">
|
||||
<div class="grid3 firstcol">
|
||||
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid4">
|
||||
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid5">
|
||||
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="white_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section psample psample_row3" id="">
|
||||
<div class="grid5 firstcol">
|
||||
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
<div class="grid7">
|
||||
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
|
||||
<div class="white_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section psample psample_row4" id="">
|
||||
<div class="grid12 firstcol">
|
||||
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
<div class="white_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="section psample psample_row1 fullreverse">
|
||||
<div class="grid2 firstcol">
|
||||
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid3">
|
||||
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid3">
|
||||
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid4">
|
||||
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="black_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section psample psample_row2 fullreverse">
|
||||
<div class="grid3 firstcol">
|
||||
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid4">
|
||||
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="grid5">
|
||||
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
|
||||
</div>
|
||||
<div class="black_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section psample fullreverse psample_row3" id="">
|
||||
<div class="grid5 firstcol">
|
||||
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
<div class="grid7">
|
||||
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
|
||||
<div class="black_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
|
||||
<div class="grid12 firstcol">
|
||||
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
|
||||
</div>
|
||||
<div class="black_blend"></div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="layout">
|
||||
|
||||
<div class="section">
|
||||
|
||||
<div class="grid12 firstcol">
|
||||
<h1>Lorem Ipsum Dolor</h1>
|
||||
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
|
||||
|
||||
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="grid8 firstcol">
|
||||
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
|
||||
|
||||
|
||||
<h3>Pellentesque ornare sem</h3>
|
||||
|
||||
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
|
||||
|
||||
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
|
||||
|
||||
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
|
||||
|
||||
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
|
||||
|
||||
<h3>Cras mattis consectetur</h3>
|
||||
|
||||
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
|
||||
|
||||
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid4 sidebar">
|
||||
|
||||
<div class="box reverse">
|
||||
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
|
||||
</div>
|
||||
|
||||
<p class="caption">Maecenas sed diam eget risus varius.</p>
|
||||
|
||||
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
|
||||
|
||||
|
||||
|
||||
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
|
||||
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="specs">
|
||||
|
||||
</div>
|
||||
|
||||
<div id="installing">
|
||||
<div class="section">
|
||||
<div class="grid7 firstcol">
|
||||
<h1>Installing Webfonts</h1>
|
||||
|
||||
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
|
||||
|
||||
<h2>1. Upload your webfonts</h2>
|
||||
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
|
||||
|
||||
<h2>2. Include the webfont stylesheet</h2>
|
||||
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="http://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
|
||||
|
||||
|
||||
<code>
|
||||
@font-face{
|
||||
font-family: 'MyWebFont';
|
||||
src: url('WebFont.eot');
|
||||
src: url('WebFont.eot?iefix') format('eot'),
|
||||
url('WebFont.woff') format('woff'),
|
||||
url('WebFont.ttf') format('truetype'),
|
||||
url('WebFont.svg#webfont') format('svg');
|
||||
}
|
||||
</code>
|
||||
|
||||
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
|
||||
<code><link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /></code>
|
||||
|
||||
<h2>3. Modify your own stylesheet</h2>
|
||||
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
|
||||
<code>p { font-family: 'MyWebFont', Arial, sans-serif; }</code>
|
||||
|
||||
<h2>4. Test</h2>
|
||||
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid5 sidebar">
|
||||
<div class="box">
|
||||
<h2>Troubleshooting<br />Font-Face Problems</h2>
|
||||
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
|
||||
|
||||
<h3>Fonts not showing in any browser</h3>
|
||||
|
||||
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
|
||||
|
||||
<h3>Fonts not loading in iPhone or iPad</h3>
|
||||
|
||||
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
|
||||
|
||||
<h3>Fonts not loading in Firefox</h3>
|
||||
|
||||
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
|
||||
|
||||
<h3>Fonts not loading in IE</h3>
|
||||
|
||||
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
|
||||
|
||||
<h3>Fonts not loading in IE9</h3>
|
||||
|
||||
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p>©2010-2011 Fontspring. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
src/fonts/dejavuserif/DejaVuSerif-webfont.eot
Executable file
3404
src/fonts/dejavuserif/DejaVuSerif-webfont.svg
Executable file
After Width: | Height: | Size: 969 KiB |