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

|

|
||||||
|
@ -11,32 +11,30 @@ option enabled and one that runs entirely in a command line environment.
|
|||||||
|
|
||||||
Let's go!
|
Let's go!
|
||||||
|
|
||||||
curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz
|
$ curl -L -O http://sourceforge.net/projects/bochs/files/bochs/2.5.1/bochs-2.5.1.tar.gz
|
||||||
tar -zxvf bochs-2.5.1.tar.gz
|
$ tar -zxvf bochs-2.5.1.tar.gz
|
||||||
mkdir build-bochs
|
$ mkdir build-bochs
|
||||||
cd build-bochs
|
$ cd build-bochs
|
||||||
|
$ ../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --with-term
|
||||||
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --with-term
|
$ make all
|
||||||
make all
|
$ make install
|
||||||
make install
|
|
||||||
{: .prettyprint .lan-sh}
|
|
||||||
|
|
||||||
And that's the terminal version. ### Important note This configure line won't
|
And that's the terminal version.
|
||||||
actually work. I'll update with the new one as soon as I can.
|
|
||||||
|
### Important note
|
||||||
|
This configure line won't actually work. I'll update with the new one as soon as
|
||||||
|
I can.
|
||||||
|
|
||||||
Now we move this to *bochs-term* and compile the version with the debugger.
|
Now we move this to *bochs-term* and compile the version with the debugger.
|
||||||
|
|
||||||
mv /usr/local/bin/bochs /usr/local/bin/bochs-term
|
$ mv /usr/local/bin/bochs /usr/local/bin/bochs-term
|
||||||
|
$ rm *
|
||||||
rm *
|
$ ../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --enable-debugger --enable-disasm --with-x11
|
||||||
../bochs-2.5.1/configure --enable-smp --enable-cpu-level=6 --enable-all-optimizations --enable-pci --enable-vmx --enable-logging --enable-fpu --enable-sb16=dummy --enable-cdrom --disable-plugins --disable-docbook --enable-debugger --enable-disasm --with-x11
|
$ make all
|
||||||
make all
|
$ make install
|
||||||
make install
|
|
||||||
{: .prettyprint .lan-sh}
|
|
||||||
|
|
||||||
Finally, copy the bochs bioses to a public place
|
Finally, copy the bochs bioses to a public place
|
||||||
|
|
||||||
cp -r ../bochs-2.5.1/bios /usr/share/bochs
|
$ cp -r ../bochs-2.5.1/bios /usr/share/bochs
|
||||||
{: .prettyprint .lan-sh}
|
|
||||||
|
|
||||||
And that's it.
|
And that's it.
|
||||||
|
@ -21,18 +21,17 @@ c-compiler. An update was required on one of my computers.
|
|||||||
Compiling gcc also requires the mpfr package to be installed. This I did with
|
Compiling gcc also requires the mpfr package to be installed. This I did with
|
||||||
[Homebrew](http://mxcl.github.com/homebrew/).
|
[Homebrew](http://mxcl.github.com/homebrew/).
|
||||||
|
|
||||||
brew install mpfr
|
:::bash
|
||||||
{: .prettyprint}
|
$ brew install mpfr
|
||||||
|
|
||||||
I downloaded all the sources I needed from [gnu.org](http://gnu.org).
|
I downloaded all the sources I needed from [gnu.org](http://gnu.org).
|
||||||
|
|
||||||
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.gz
|
:::bash
|
||||||
curl -O http://ftp.gnu.org/gnu/gcc/gcc-4.6.3/gcc-core-4.6.3.tar.gz
|
$ curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.gz
|
||||||
|
$ curl -O http://ftp.gnu.org/gnu/gcc/gcc-4.6.3/gcc-core-4.6.3.tar.gz
|
||||||
curl -O http://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
|
$ curl -O http://ftp.gnu.org/gnu/gmp/gmp-5.0.2.tar.gz
|
||||||
curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.tar.gz
|
$ curl -O http://ftp.gnu.org/gnu/mpfr/mpfr-3.1.0.tar.gz
|
||||||
curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
|
$ curl -O http://www.multiprecision.org/mpc/download/mpc-0.9.tar.gz
|
||||||
{: .prettyprint .lang-bash}
|
|
||||||
|
|
||||||
Feel free to use later versions, but if you do, I cannot guarantee that the
|
Feel free to use later versions, but if you do, I cannot guarantee that the
|
||||||
code posted in my logs will work for you (it's very likely to work, but not
|
code posted in my logs will work for you (it's very likely to work, but not
|
||||||
@ -40,33 +39,32 @@ code posted in my logs will work for you (it's very likely to work, but not
|
|||||||
gcc, so after extracting all archives, they are simply copied into the gcc
|
gcc, so after extracting all archives, they are simply copied into the gcc
|
||||||
source
|
source
|
||||||
|
|
||||||
mv gmp-5.0.2 gcc-4.6.3/gmp
|
:::bash
|
||||||
mv mpfr-3.1.0 gcc-4.6.3/mpfr
|
$ mv gmp-5.0.2 gcc-4.6.3/gmp
|
||||||
mv mpc-0.9 gcc-4.6.3/mpc
|
$ mv mpfr-3.1.0 gcc-4.6.3/mpfr
|
||||||
{: .prettyprint .lang-sh}
|
$ mv mpc-0.9 gcc-4.6.3/mpc
|
||||||
|
|
||||||
In order not to mess up the source, binutils and gcc were built out of tree.
|
In order not to mess up the source, binutils and gcc were built out of tree.
|
||||||
|
|
||||||
mkdir build-binutils
|
:::bash
|
||||||
cd build-binutils
|
$ mkdir build-binutils
|
||||||
|
$ cd build-binutils
|
||||||
export PREFIX=/usr/local/cross
|
$ export PREFIX=/usr/local/cross
|
||||||
export TARGET=i386-elf
|
$ export TARGET=i386-elf
|
||||||
../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
|
$ ../binutils-2.22/configure --target=$TARGET --prefix=$PREFIX --disable-nls
|
||||||
make all
|
$ make all
|
||||||
make install
|
$ make install
|
||||||
{: .prettyprint .lang-bash}
|
|
||||||
|
|
||||||
And the same for gcc, using the new binutils
|
And the same for gcc, using the new binutils
|
||||||
|
|
||||||
cd ..
|
:::bash
|
||||||
mkdir build-gcc
|
$ cd ..
|
||||||
cd build-gcc
|
$ mkdir build-gcc
|
||||||
export PATH=$PATH:$PREFIX/bin
|
$ cd build-gcc
|
||||||
../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
|
$ export PATH=$PATH:$PREFIX/bin
|
||||||
make all-gcc
|
$ ../gcc-4.6.3/configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers
|
||||||
make install-gcc
|
$ make all-gcc
|
||||||
{: .prettyprint .lang-bash}
|
$ make install-gcc
|
||||||
|
|
||||||
It's really important to run _make all-gcc_ and _make install-gcc_ and __not__
|
It's really important to run _make all-gcc_ and _make install-gcc_ and __not__
|
||||||
_make all_ and _make install_ here. It probably works anyway - if you ever
|
_make all_ and _make install_ here. It probably works anyway - if you ever
|
||||||
|
@ -12,11 +12,11 @@ First point today was making a directory structure and creating a [git
|
|||||||
repository](http://github.com/thomasloven/os5). The directory structure starts
|
repository](http://github.com/thomasloven/os5). The directory structure starts
|
||||||
off as follows
|
off as follows
|
||||||
|
|
||||||
os5/
|
os5/
|
||||||
|-- build/
|
|-- build/
|
||||||
|-- include/
|
|-- include/
|
||||||
`-- kernel/
|
`-- kernel/
|
||||||
`-- include/
|
`-- include/
|
||||||
|
|
||||||
A difference from previous times is that the _include_ directory is outside the
|
A difference from previous times is that the _include_ directory is outside the
|
||||||
kernel directory. That's because last time I realized I had a lot of include
|
kernel directory. That's because last time I realized I had a lot of include
|
||||||
@ -24,12 +24,12 @@ files that were used both in the kernel and many other parts. I'll probably
|
|||||||
move it into some library directory later. Know what? Let's move it into
|
move it into some library directory later. Know what? Let's move it into
|
||||||
a library directory right now...
|
a library directory right now...
|
||||||
|
|
||||||
os5/
|
os5/
|
||||||
|-- build/
|
|-- build/
|
||||||
|-- kernel/
|
|-- kernel/
|
||||||
| `-- include/
|
| `-- include/
|
||||||
`-- library/
|
`-- library/
|
||||||
`-- include/
|
`-- include/
|
||||||
|
|
||||||
The build/ directory contains some scripts needed for building and testing the
|
The build/ directory contains some scripts needed for building and testing the
|
||||||
os as well as a floppy image preinstalled with the GRUB bootloader.
|
os as well as a floppy image preinstalled with the GRUB bootloader.
|
||||||
@ -38,83 +38,83 @@ Next, basic Makefiles were added to os5/ and kernel/. The standard procedure of
|
|||||||
the main makefile is to run the makefile in kernel/, copy the kernel image into
|
the main makefile is to run the makefile in kernel/, copy the kernel image into
|
||||||
the floppy and then run bochs-term.
|
the floppy and then run bochs-term.
|
||||||
|
|
||||||
|
|
||||||
BUILDDIR := $(PWD)
|
|
||||||
|
|
||||||
PATH := /usr/local/cross/bin:$(PATH)
|
:::make
|
||||||
DIRS := kernel
|
BUILDDIR := $(PWD)
|
||||||
TARGET := i386-elf
|
|
||||||
|
|
||||||
AS := nasm
|
PATH := /usr/local/cross/bin:$(PATH)
|
||||||
CC := i386-elf-gcc
|
DIRS := kernel
|
||||||
LD := i386-elf-ld
|
TARGET := i386-elf
|
||||||
|
|
||||||
ASFLAGS := -f elf
|
|
||||||
CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
|
|
||||||
CCFLAGS += -fomit-frame-pointer -fno-asynchronous-unwind-tables
|
|
||||||
CCFLAGS += -fno-unwind-tables -I$(BUILDDIR)/library/include
|
|
||||||
LDFLAGS := -T $(BUILDDIR)/library/include/Link.ld
|
|
||||||
|
|
||||||
export BUILDDIR AS CC LD ASFLAGS CCFLAGS LDFLAGS
|
|
||||||
|
|
||||||
.SILENT:
|
|
||||||
|
|
||||||
.PHONY: $(DIRS) floppy emul
|
|
||||||
.default: all floppy emul
|
|
||||||
|
|
||||||
l: all floppy emul
|
|
||||||
|
|
||||||
all: $(DIRS)
|
|
||||||
|
|
||||||
$(DIRS): force
|
|
||||||
@echo " MAKE " $@
|
|
||||||
@cd $@; $(MAKE) $(MFLAGS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@for DIR in $(DIRS); do echo " CLEAN " $$DIR; cd $(BUILDDIR)/$$DIR; make clean; done;
|
|
||||||
|
|
||||||
floppy: force
|
|
||||||
@echo " UPDATING IMAGE"
|
|
||||||
@build/update_image.sh
|
|
||||||
|
|
||||||
emul: force
|
AS := nasm
|
||||||
@echo " STARTING EMULATOR"
|
CC := i386-elf-gcc
|
||||||
@build/emul.sh
|
LD := i386-elf-ld
|
||||||
|
|
||||||
force:
|
ASFLAGS := -f elf
|
||||||
true
|
CCFLAGS := -nostdlib -nostdinc -fno-builtin -fno-exceptions -m32
|
||||||
{: .prettyprint}
|
CCFLAGS += -fomit-frame-pointer -fno-asynchronous-unwind-tables
|
||||||
|
CCFLAGS += -fno-unwind-tables -I$(BUILDDIR)/library/include
|
||||||
|
LDFLAGS := -T $(BUILDDIR)/library/include/Link.ld
|
||||||
|
|
||||||
|
export BUILDDIR AS CC LD ASFLAGS CCFLAGS LDFLAGS
|
||||||
|
|
||||||
|
.SILENT:
|
||||||
|
|
||||||
|
.PHONY: $(DIRS) floppy emul
|
||||||
|
.default: all floppy emul
|
||||||
|
|
||||||
|
l: all floppy emul
|
||||||
|
|
||||||
|
all: $(DIRS)
|
||||||
|
|
||||||
|
$(DIRS): force
|
||||||
|
@echo " MAKE " $@
|
||||||
|
@cd $@; $(MAKE) $(MFLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@for DIR in $(DIRS); do echo " CLEAN " $$DIR; cd $(BUILDDIR)/$$DIR; make clean; done;
|
||||||
|
|
||||||
|
floppy: force
|
||||||
|
@echo " UPDATING IMAGE"
|
||||||
|
@build/update_image.sh
|
||||||
|
|
||||||
|
emul: force
|
||||||
|
@echo " STARTING EMULATOR"
|
||||||
|
@build/emul.sh
|
||||||
|
|
||||||
|
force:
|
||||||
|
true
|
||||||
|
|
||||||
The makefile in the kernel/ directory is pretty much straight forward.
|
The makefile in the kernel/ directory is pretty much straight forward.
|
||||||
|
|
||||||
|
|
||||||
TARGET := kernel
|
:::make
|
||||||
SUBDIR := kernel
|
TARGET := kernel
|
||||||
|
SUBDIR := kernel
|
||||||
SOURCES := kinit.o boot.o
|
|
||||||
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s))
|
SOURCES := kinit.o boot.o
|
||||||
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c))
|
SOURCES += $(patsubst %.s,%.o,$(shell find . -name "*.s" | grep -v boot.s))
|
||||||
|
SOURCES += $(patsubst %.c,%.0,$(shell find . -name "*.c" | grep -v kinit.c))
|
||||||
CCFLAGS += -Iinclude
|
|
||||||
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
|
CCFLAGS += -Iinclude
|
||||||
|
LDFLAGS := -T $(BUILDDIR)/$(SUBDIR)/include/Link.ld
|
||||||
.SUFFICES: .o .s .c
|
|
||||||
|
.SUFFICES: .o .s .c
|
||||||
all: $(TARGET)
|
|
||||||
|
all: $(TARGET)
|
||||||
$(TARGET): $(SOURCES)
|
|
||||||
@echo " ln " $(TARGET)
|
$(TARGET): $(SOURCES)
|
||||||
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
|
@echo " ln " $(TARGET)
|
||||||
|
@$(LD) $(LDFLAGS) -o $(TARGET) $(SOURCES)
|
||||||
.c.o:
|
|
||||||
@echo " gcc " $<
|
.c.o:
|
||||||
@$(CC) $(CFLAGS) -c $< -o $@
|
@echo " gcc " $<
|
||||||
|
@$(CC) $(CFLAGS) -c $< -o $@
|
||||||
.s.o:
|
|
||||||
@echo " nasm " $<
|
.s.o:
|
||||||
@$(AS) $(ASFLAGS) $< -o $@
|
@echo " nasm " $<
|
||||||
|
@$(AS) $(ASFLAGS) $< -o $@
|
||||||
clean:
|
|
||||||
-@rm $(SOURCES) 2>/dev/null
|
clean:
|
||||||
-@rm $(TARGET) 2>/dev/null
|
-@rm $(SOURCES) 2>/dev/null
|
||||||
{: .prettyprint}
|
-@rm $(TARGET) 2>/dev/null
|
||||||
|
@ -19,34 +19,33 @@ a special Linker file for the kernel.
|
|||||||
|
|
||||||
*kernel/include/Link.ld*
|
*kernel/include/Link.ld*
|
||||||
|
|
||||||
ENTRY(start)
|
ENTRY(start)
|
||||||
|
|
||||||
SECTIONS {
|
SECTIONS {
|
||||||
. = 0xC0100000;
|
. = 0xC0100000;
|
||||||
.text : AT(ADDR(.text) - 0xC0000000)
|
.text : AT(ADDR(.text) - 0xC0000000)
|
||||||
{
|
{
|
||||||
code = .; _code = .; __code = .;
|
code = .; _code = .; __code = .;
|
||||||
*(.text)
|
*(.text)
|
||||||
*(.eh_frame)
|
*(.eh_frame)
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
}
|
}
|
||||||
.data : AT(ADDR(.data) - 0xC0000000)
|
.data : AT(ADDR(.data) - 0xC0000000)
|
||||||
{
|
{
|
||||||
data = .; _data = .; __data = .;
|
data = .; _data = .; __data = .;
|
||||||
*(.data)
|
*(.data)
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
}
|
}
|
||||||
.bss : AT(ADDR(.bss) - 0xC0000000)
|
.bss : AT(ADDR(.bss) - 0xC0000000)
|
||||||
{
|
{
|
||||||
bss = .; _bss = .; __bss = .;
|
bss = .; _bss = .; __bss = .;
|
||||||
*(.bss)
|
*(.bss)
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
}
|
}
|
||||||
|
|
||||||
_end = .;
|
_end = .;
|
||||||
}
|
}
|
||||||
{: .prettyprint .linenums}
|
|
||||||
|
|
||||||
GRUB drops us off at the kernel entry point - *start* as defined in the
|
GRUB drops us off at the kernel entry point - *start* as defined in the
|
||||||
linkfile - without paging and with an unknown GDT. So setting that up will be
|
linkfile - without paging and with an unknown GDT. So setting that up will be
|
||||||
@ -54,30 +53,30 @@ the first order of business. This is done in the assembly bootstrap.
|
|||||||
|
|
||||||
*kernel/boot.s*
|
*kernel/boot.s*
|
||||||
|
|
||||||
; Kernel start point
|
:::nasm
|
||||||
[global start]
|
; Kernel start point
|
||||||
start:
|
[global start]
|
||||||
cli
|
start:
|
||||||
|
cli
|
||||||
; Load page directory and enable paging
|
|
||||||
mov ecx, BootPageDirectory - KERNEL_OFFSET
|
; Load page directory and enable paging
|
||||||
mov cr3, ecx
|
mov ecx, BootPageDirectory - KERNEL_OFFSET
|
||||||
mov ecx, cr0
|
mov cr3, ecx
|
||||||
or ecx, 0x80000000
|
mov ecx, cr0
|
||||||
mov cr0, ecx
|
or ecx, 0x80000000
|
||||||
lea ecx, [.higherHalf]
|
mov cr0, ecx
|
||||||
jmp ecx
|
lea ecx, [.higherHalf]
|
||||||
|
jmp ecx
|
||||||
.higherHalf:
|
|
||||||
; Load GDT
|
.higherHalf:
|
||||||
mov ecx, gdt_ptr
|
; Load GDT
|
||||||
lgdt [ecx]
|
mov ecx, gdt_ptr
|
||||||
|
lgdt [ecx]
|
||||||
SetSegments 0x10, cx
|
|
||||||
jmp 0x8:.gdtLoaded
|
SetSegments 0x10, cx
|
||||||
|
jmp 0x8:.gdtLoaded
|
||||||
.gdtLoaded:
|
|
||||||
{: .prettyprint .lang-nasm .linenums:61}
|
.gdtLoaded:
|
||||||
|
|
||||||
Here's another new thing for me. Macros. Can't believe I could do without them
|
Here's another new thing for me. Macros. Can't believe I could do without them
|
||||||
before. *SetSegments* is a macro that in this case loads 0x10 into ecx and then
|
before. *SetSegments* is a macro that in this case loads 0x10 into ecx and then
|
||||||
@ -86,94 +85,93 @@ which also contains some constants.
|
|||||||
|
|
||||||
*kernel/include/asm_macros.inc*
|
*kernel/include/asm_macros.inc*
|
||||||
|
|
||||||
; GRUB multiboot headers
|
:::nasm
|
||||||
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
|
; GRUB multiboot headers
|
||||||
MBOOT_MEMORY_INFO_FLAG equ 1<<1
|
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
|
||||||
MBOOT_HEADER_MAGIC equ 0x1BADB002
|
MBOOT_MEMORY_INFO_FLAG equ 1<<1
|
||||||
|
MBOOT_HEADER_MAGIC equ 0x1BADB002
|
||||||
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
|
|
||||||
MBOOT_HEADER_CHECKSUM equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)
|
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
|
||||||
|
MBOOT_HEADER_CHECKSUM equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)
|
||||||
|
|
||||||
KERNEL_OFFSET equ 0xC0000000
|
|
||||||
BOOT_STACK_SIZE equ 0x1FFF
|
KERNEL_OFFSET equ 0xC0000000
|
||||||
|
BOOT_STACK_SIZE equ 0x1FFF
|
||||||
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
|
|
||||||
%macro SetSegments 2
|
; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
|
||||||
mov e%2, %1
|
%macro SetSegments 2
|
||||||
mov ds, %2
|
mov e%2, %1
|
||||||
mov es, %2
|
mov ds, %2
|
||||||
mov fs, %2
|
mov es, %2
|
||||||
mov gs, %2
|
mov fs, %2
|
||||||
mov ss, %2
|
mov gs, %2
|
||||||
%endmacro
|
mov ss, %2
|
||||||
{: .prettyprint .lang-nasm .linenums:2}
|
%endmacro
|
||||||
|
|
||||||
There are also references to some data structures, i.e. *BootPageDirectory* and
|
There are also references to some data structures, i.e. *BootPageDirectory* and
|
||||||
*gdt_ptr*. Those are hardcoded in the bootstrap file.
|
*gdt_ptr*. Those are hardcoded in the bootstrap file.
|
||||||
|
|
||||||
*kernel/boot.s*
|
*kernel/boot.s*
|
||||||
|
|
||||||
|
:::nasm
|
||||||
%include "include/asm_macros.inc"
|
%include "include/asm_macros.inc"
|
||||||
|
|
||||||
[bits 32]
|
[bits 32]
|
||||||
|
|
||||||
section .bss
|
section .bss
|
||||||
|
|
||||||
align 0x8
|
align 0x8
|
||||||
|
|
||||||
; Stack for booting
|
; Stack for booting
|
||||||
[global BootStack]
|
[global BootStack]
|
||||||
BootStackTop:
|
BootStackTop:
|
||||||
resb BOOT_STACK_SIZE
|
resb BOOT_STACK_SIZE
|
||||||
BootStack:
|
BootStack:
|
||||||
|
|
||||||
section .data
|
section .data
|
||||||
|
|
||||||
align 0x1000
|
align 0x1000
|
||||||
|
|
||||||
; Page directory for booting up.
|
; Page directory for booting up.
|
||||||
; First four megabytes are identity mapped as well as
|
; First four megabytes are identity mapped as well as
|
||||||
; mapped to 0xC0000000
|
; mapped to 0xC0000000
|
||||||
[global BootPageDirectory]
|
[global BootPageDirectory]
|
||||||
BootPageDirectory:
|
BootPageDirectory:
|
||||||
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
||||||
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
|
times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
|
||||||
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
dd (BootPageTable - KERNEL_OFFSET) + 0x3
|
||||||
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
|
times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
|
||||||
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
|
dd (BootPageDirectory - KERNEL_OFFSET) + 0x3
|
||||||
|
|
||||||
BootPageTable:
|
BootPageTable:
|
||||||
%assign i 0
|
%assign i 0
|
||||||
%rep 1024
|
%rep 1024
|
||||||
dd (i << 12) | 0x3
|
dd (i << 12) | 0x3
|
||||||
%assign i i+1
|
%assign i i+1
|
||||||
%endrep
|
%endrep
|
||||||
|
|
||||||
; Hard-coded GDT.
|
; Hard-coded GDT.
|
||||||
; GDT pointer is wrapped into the first entry
|
; GDT pointer is wrapped into the first entry
|
||||||
[global gdt]
|
[global gdt]
|
||||||
gdt_ptr:
|
gdt_ptr:
|
||||||
gdt:
|
gdt:
|
||||||
dw 0x002F
|
dw 0x002F
|
||||||
dd gdt
|
dd gdt
|
||||||
dw 0x0000
|
dw 0x0000
|
||||||
dd 0x0000FFFF, 0x00CF9A00
|
dd 0x0000FFFF, 0x00CF9A00
|
||||||
dd 0x0000FFFF, 0x00CF9200
|
dd 0x0000FFFF, 0x00CF9200
|
||||||
dd 0x0000FFFF, 0x00CFFA00
|
dd 0x0000FFFF, 0x00CFFA00
|
||||||
dd 0x0000FFFF, 0x00CFF200
|
dd 0x0000FFFF, 0x00CFF200
|
||||||
|
|
||||||
section .text
|
section .text
|
||||||
|
|
||||||
align 4
|
align 4
|
||||||
|
|
||||||
; GRUB Multiboot data
|
; GRUB Multiboot data
|
||||||
MultiBootHeader:
|
MultiBootHeader:
|
||||||
dd MBOOT_HEADER_MAGIC
|
dd MBOOT_HEADER_MAGIC
|
||||||
dd MBOOT_HEADER_FLAGS
|
dd MBOOT_HEADER_FLAGS
|
||||||
dd MBOOT_HEADER_CHECKSUM
|
dd MBOOT_HEADER_CHECKSUM
|
||||||
{: .prettyprint .lang-nasm .linenums:2}
|
|
||||||
|
|
||||||
Well. This is first of all some area saved for a stack. Then there's the page
|
Well. This is first of all some area saved for a stack. Then there's the page
|
||||||
directory which has the same page table at virtual memory 0x00000000 and
|
directory which has the same page table at virtual memory 0x00000000 and
|
||||||
@ -191,29 +189,29 @@ from the page directory.
|
|||||||
|
|
||||||
*kernel/boot.s*
|
*kernel/boot.s*
|
||||||
|
|
||||||
.gdtLoaded:
|
:::nasm
|
||||||
; Clear the identity mapping from the page directory
|
.gdtLoaded:
|
||||||
mov edx, BootPageDirectory
|
; Clear the identity mapping from the page directory
|
||||||
xor ecx, ecx
|
mov edx, BootPageDirectory
|
||||||
mov [edx], ecx
|
xor ecx, ecx
|
||||||
invlpg[0]
|
mov [edx], ecx
|
||||||
|
invlpg[0]
|
||||||
; Load a stack for booting
|
|
||||||
mov esp, BootStack
|
; Load a stack for booting
|
||||||
mov ebp, BootStack
|
mov esp, BootStack
|
||||||
|
mov ebp, BootStack
|
||||||
; eax contains the magic number from GRUB 0x2BADB002
|
|
||||||
push eax
|
; eax contains the magic number from GRUB 0x2BADB002
|
||||||
|
push eax
|
||||||
; ebx contains the address of the Multiboot information structure
|
|
||||||
add ebx, KERNEL_OFFSET
|
; ebx contains the address of the Multiboot information structure
|
||||||
push ebx
|
add ebx, KERNEL_OFFSET
|
||||||
|
push ebx
|
||||||
; Call the c function for setting up
|
|
||||||
[extern kinit]
|
; Call the c function for setting up
|
||||||
call kinit
|
[extern kinit]
|
||||||
jmp $
|
call kinit
|
||||||
{: .prettyprint .lang-nasm .linenums:83}
|
jmp $
|
||||||
|
|
||||||
The final thing we do before jumping into the c kernel stub is push the values
|
The final thing we do before jumping into the c kernel stub is push the values
|
||||||
of eax and ebx which contains the multiboot magic number and information
|
of eax and ebx which contains the multiboot magic number and information
|
||||||
@ -223,12 +221,11 @@ The only thing that's left now in order to get this to run is the c stub.
|
|||||||
|
|
||||||
*kernel/kinit.c*
|
*kernel/kinit.c*
|
||||||
|
|
||||||
|
:::c
|
||||||
void kinit()
|
void kinit()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
{: .prettyprint .linenums:2}
|
|
||||||
|
|
||||||
Compiling and running this through bochs, we are presented with a black and
|
Compiling and running this through bochs, we are presented with a black and
|
||||||
white screen completely void of error messages. Perfect!
|
white screen completely void of error messages. Perfect!
|
||||||
|
@ -6,13 +6,14 @@ tags: [osdev]
|
|||||||
Something that always annoyed me is how hard it is to synchronize constants
|
Something that always annoyed me is how hard it is to synchronize constants
|
||||||
between assembly and c code. In assembler, you define a constant value as
|
between assembly and c code. In assembler, you define a constant value as
|
||||||
|
|
||||||
EXACT_PI equ 3
|
:::nasm
|
||||||
{: .prettyprint .lang-nasm}
|
EXACT_PI equ 3
|
||||||
|
|
||||||
and in c
|
and in c
|
||||||
|
|
||||||
#define EXACT_PI 3
|
:::c
|
||||||
{: .prettyprint .lang-c}
|
#define EXACT_PI 3
|
||||||
|
|
||||||
As is usually the case with things that annoy me, there is of course a solution
|
As is usually the case with things that annoy me, there is of course a solution
|
||||||
to this, as I found out today. The solution is the c preprocessor.
|
to this, as I found out today. The solution is the c preprocessor.
|
||||||
|
|
||||||
@ -30,27 +31,27 @@ Well, here's a minimal (non-working) example:
|
|||||||
|
|
||||||
_myAsmFile.asm_
|
_myAsmFile.asm_
|
||||||
|
|
||||||
#include <header.h>
|
:::nasm
|
||||||
|
#include <header.h>
|
||||||
mov eax, EXACT_PI
|
|
||||||
{: .prettyprint .lang-nasm}
|
mov eax, EXACT_PI
|
||||||
|
|
||||||
_include/header.h_
|
_include/header.h_
|
||||||
|
|
||||||
#pragma once
|
:::c
|
||||||
|
#pragma once
|
||||||
#define EXACT_PI 3
|
|
||||||
|
#define EXACT_PI 3
|
||||||
#ifndef __ASSEMBLER__
|
|
||||||
// This is not evaluated if header.h is included from an assembly file.
|
#ifndef __ASSEMBLER__
|
||||||
#endif
|
// This is not evaluated if header.h is included from an assembly file.
|
||||||
{: .prettyprint .lang-c}
|
#endif
|
||||||
|
|
||||||
This is compiled through:
|
This is compiled through:
|
||||||
|
|
||||||
cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
|
:::bash
|
||||||
nasm myAsmFile.s
|
$ cpp -I include -x assembler-with-cpp myAsmFile.asm -o myAsmFile.s
|
||||||
{: .prettyprint}
|
$ nasm myAsmFile.s
|
||||||
|
|
||||||
The _-x_-flag tells the preprocessor what type of file the following input
|
The _-x_-flag tells the preprocessor what type of file the following input
|
||||||
files are. _assembler-with-cpp_ means _cpp_ will ignore everything but the
|
files are. _assembler-with-cpp_ means _cpp_ will ignore everything but the
|
||||||
|
@ -32,16 +32,21 @@ that each page only has room for four pointers. The figure below shows eight
|
|||||||
physical pages of a such computer. The two leftmost pages are used by the
|
physical pages of a such computer. The two leftmost pages are used by the
|
||||||
physical memory manager for the free page stack. The stack contains pointers to
|
physical memory manager for the free page stack. The stack contains pointers to
|
||||||
the next five pages, who are free. The rightmost page is handed out.
|
the next five pages, who are free. The rightmost page is handed out.
|
||||||
|
|
||||||
{: .noborder .center}
|
{: .noborder .center}
|
||||||
|
|
||||||
When the _pmm_ receives a request for a memory page it will pop the topmost
|
When the _pmm_ receives a request for a memory page it will pop the topmost
|
||||||
entry from the stack and returns, in this case, the second rightmost page to
|
entry from the stack and returns, in this case, the second rightmost page to
|
||||||
the caller. {: .noborder .center}
|
the caller.
|
||||||
|
|
||||||
|
{: .noborder .center}
|
||||||
|
|
||||||
The next time the pmm receives a request for a memory page it will notice that
|
The next time the pmm receives a request for a memory page it will notice that
|
||||||
an entire page of the stack is empty and just being wasted, so it will shrink
|
an entire page of the stack is empty and just being wasted, so it will shrink
|
||||||
its stack by one page-size and return the address of the page that previously
|
its stack by one page-size and return the address of the page that previously
|
||||||
made up the top of the stack. {: .noborder .center}
|
made up the top of the stack
|
||||||
|
|
||||||
|
{: .noborder .center}
|
||||||
|
|
||||||
Likewise, if the stack is full of pointers when a used page is handed back,
|
Likewise, if the stack is full of pointers when a used page is handed back,
|
||||||
that page is used to increase the stack space. Through the use of virtual
|
that page is used to increase the stack space. Through the use of virtual
|
||||||
|
@ -54,7 +54,9 @@ looking up the fourth group in the page directory. It finds the address of the
|
|||||||
page directory and assumes this is the page table for the group. It caries on,
|
page directory and assumes this is the page table for the group. It caries on,
|
||||||
looking up the second entry in this 'page table' and gets the address of the
|
looking up the second entry in this 'page table' and gets the address of the
|
||||||
page it wants. This happens to be the address of the page table for the second
|
page it wants. This happens to be the address of the page table for the second
|
||||||
group. {: .center .noborder}
|
group.
|
||||||
|
|
||||||
|
{: .center .noborder}
|
||||||
|
|
||||||
In other words, you can access any page table through a fixed address in
|
In other words, you can access any page table through a fixed address in
|
||||||
memory. But wait, it gets even better.
|
memory. But wait, it gets even better.
|
||||||
@ -64,11 +66,13 @@ virtual memory. The MMU will look up the last entry in the page directory and
|
|||||||
get the address of the page directory. It will then look up the last entry in
|
get the address of the page directory. It will then look up the last entry in
|
||||||
the page directory and get the address of the page directory (that's not a typo
|
the page directory and get the address of the page directory (that's not a typo
|
||||||
- I meant to write the same thing twice). This lets you access the page
|
- I meant to write the same thing twice). This lets you access the page
|
||||||
directory too through a fixed memory address. {: .center .noborder}
|
directory too through a fixed memory address.
|
||||||
|
|
||||||
|
{: .center .noborder}
|
||||||
|
|
||||||
###Some considerations
|
###Some considerations
|
||||||
An important question to put at this point is whether a recursive page
|
An important question to put at this point is whether a recursive page
|
||||||
directory is really a good idea.
|
directory is really a good idea.
|
||||||
|
|
||||||
In our imaginary computer with its really small address space, we notice that
|
In our imaginary computer with its really small address space, we notice that
|
||||||
the page table and directories now reserve a quarter of the entire available
|
the page table and directories now reserve a quarter of the entire available
|
||||||
@ -78,7 +82,7 @@ address space, though, which is more reasonable. Then again, if your computer
|
|||||||
has 4 gigabytes of physical RAM, this would mean there is four megabytes of it
|
has 4 gigabytes of physical RAM, this would mean there is four megabytes of it
|
||||||
that can't be used. Then again again, if you have easy access to your page
|
that can't be used. Then again again, if you have easy access to your page
|
||||||
tables - such as through a recursive page directory - you can just page in
|
tables - such as through a recursive page directory - you can just page in
|
||||||
those 4 megabytes as needed.
|
those 4 megabytes as needed.
|
||||||
|
|
||||||
There are other simple ways of accessing the page directory and tables. For
|
There are other simple ways of accessing the page directory and tables. For
|
||||||
example, if you just keep track of the page directory and one page table it's
|
example, if you just keep track of the page directory and one page table it's
|
||||||
@ -91,16 +95,16 @@ tool, others don't.
|
|||||||
Finally, if a recursive page directory is used on an x86, the following can be
|
Finally, if a recursive page directory is used on an x86, the following can be
|
||||||
used to access the page directories and tables:
|
used to access the page directories and tables:
|
||||||
|
|
||||||
uint32_t *page_dir = 0xFFFFF000;
|
:::c
|
||||||
uint32_t *page_tables = 0xFFC00000;
|
uint32_t *page_dir = 0xFFFFF000;
|
||||||
|
uint32_t *page_tables = 0xFFC00000;
|
||||||
//addr = virtual address
|
|
||||||
//phys = physical address (page alligned)
|
//addr = virtual address
|
||||||
//flags = access flags
|
//phys = physical address (page alligned)
|
||||||
|
//flags = access flags
|
||||||
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
|
|
||||||
page_tables[addr >> 12] = phys | flags;
|
page_dir[addr >> 22] = &page_tables[addr >> 12] | flags;
|
||||||
{:.prettyprint}
|
page_tables[addr >> 12] = phys | flags;
|
||||||
|
|
||||||
###Git
|
###Git
|
||||||
A recursive page directory has been implemented in Git commit
|
A recursive page directory has been implemented in Git commit
|
||||||
|
@ -26,19 +26,19 @@ Now, let's look at a few ways to do this.
|
|||||||
The simplest possible memory manager just hands out a new address each time
|
The simplest possible memory manager just hands out a new address each time
|
||||||
_malloc_ is called and doesn't care when memory is freed.
|
_malloc_ is called and doesn't care when memory is freed.
|
||||||
|
|
||||||
uint32_t memory_pointer = HEAP_START;
|
:::c
|
||||||
|
uint32_t memory_pointer = HEAP_START;
|
||||||
|
|
||||||
void *malloc(uint32_t size);
|
void *malloc(uint32_t size);
|
||||||
{
|
{
|
||||||
memory_pointer = memory_pointer + size;
|
memory_pointer = memory_pointer + size;
|
||||||
return memory_pointer - size;
|
return memory_pointer - size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free(void *mem)
|
void free(void *mem)
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
{: .prettyprint}
|
|
||||||
|
|
||||||
###Heap
|
###Heap
|
||||||
The next method - which I prefer - is a memory heap.
|
The next method - which I prefer - is a memory heap.
|
||||||
@ -46,33 +46,33 @@ The next method - which I prefer - is a memory heap.
|
|||||||
The heap consists of a list of free memory areas. In the simplest possible
|
The heap consists of a list of free memory areas. In the simplest possible
|
||||||
variety, it would look something like this:
|
variety, it would look something like this:
|
||||||
|
|
||||||
struct area_header
|
:::c
|
||||||
{
|
struct area_header
|
||||||
uint32_t size;
|
{
|
||||||
struct free_area *next;
|
uint32_t size;
|
||||||
};
|
struct free_area *next;
|
||||||
|
};
|
||||||
void *malloc(uint32_t size)
|
|
||||||
{
|
void *malloc(uint32_t size)
|
||||||
struct area_header *area = heap_list_head;
|
{
|
||||||
while(area)
|
struct area_header *area = heap_list_head;
|
||||||
{
|
while(area)
|
||||||
if(area->size >= size)
|
{
|
||||||
{
|
if(area->size >= size)
|
||||||
remove_from_heap_list(area);
|
{
|
||||||
return get_memory_area(area);
|
remove_from_heap_list(area);
|
||||||
}
|
return get_memory_area(area);
|
||||||
area = area->next;
|
}
|
||||||
}
|
area = area->next;
|
||||||
panic("Out of memory!");
|
}
|
||||||
}
|
panic("Out of memory!");
|
||||||
|
}
|
||||||
void free(void *mem)
|
|
||||||
{
|
void free(void *mem)
|
||||||
struct area_header area = get_area_header(mem);
|
{
|
||||||
insert_into_heap_list(area);
|
struct area_header area = get_area_header(mem);
|
||||||
}
|
insert_into_heap_list(area);
|
||||||
{: .prettyprint}
|
}
|
||||||
|
|
||||||
Here it is assumed that the free memory is already divided into smaller areas
|
Here it is assumed that the free memory is already divided into smaller areas
|
||||||
that each start with an *area_header* structure as in the figure below. If the
|
that each start with an *area_header* structure as in the figure below. If the
|
||||||
|
@ -7,8 +7,8 @@ tags: [osdev]
|
|||||||
Today I was writing some code for handling interrupts.
|
Today I was writing some code for handling interrupts.
|
||||||
At one point I needed the following piece of code
|
At one point I needed the following piece of code
|
||||||
|
|
||||||
extern void isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void), isr47(void);
|
:::c
|
||||||
{: .prettyprint}
|
extern void isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void), isr47(void);
|
||||||
|
|
||||||
###The solution
|
###The solution
|
||||||
Vim macros.
|
Vim macros.
|
||||||
@ -17,7 +17,7 @@ I've been using this site and my rewrite of my operating system as an excuse to
|
|||||||
learn vim. And today it payed off. To write the above piece of code I used the
|
learn vim. And today it payed off. To write the above piece of code I used the
|
||||||
key presses
|
key presses
|
||||||
|
|
||||||
iisr0(void),<esc>0qayyp3l<ctrl>a0q46@a47k48J$r;Iextern void <esc>
|
iisr0(void),<esc>0qayyp3l<ctrl>a0q46@a47k48J$r;Iextern void <esc>
|
||||||
|
|
||||||
Couldn't be easier!
|
Couldn't be easier!
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ OK, so maybe it could... Let's break it down.
|
|||||||
|
|
||||||
Let's start with
|
Let's start with
|
||||||
|
|
||||||
iisr0(void),<esc>
|
iisr0(void),<esc>
|
||||||
|
|
||||||
_i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave
|
_i_ puts vim in Insert mode. There we write _isr0(void),_ and finally leave
|
||||||
Insert mode with the escape key.
|
Insert mode with the escape key.
|
||||||
@ -33,7 +33,7 @@ Insert mode with the escape key.
|
|||||||
Next is _0_ to bring the pointer to the beginning of the line. Then comes the
|
Next is _0_ to bring the pointer to the beginning of the line. Then comes the
|
||||||
macro.
|
macro.
|
||||||
|
|
||||||
qayyp3l<ctrl>a0q
|
qayyp3l<ctrl>a0q
|
||||||
|
|
||||||
_qa_ starts recording a macro into register a.
|
_qa_ starts recording a macro into register a.
|
||||||
|
|
||||||
@ -48,13 +48,13 @@ recording.
|
|||||||
|
|
||||||
The next part:
|
The next part:
|
||||||
|
|
||||||
46@a47k48J
|
46@a47k48J
|
||||||
|
|
||||||
runs the macro 46 times, steps up 47 times and joins the current line with the
|
runs the macro 46 times, steps up 47 times and joins the current line with the
|
||||||
next 48 times. We now have
|
next 48 times. We now have
|
||||||
|
|
||||||
isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void),
|
:::c
|
||||||
{: .prettyprint}
|
isr0(void), isr1(void), isr2(void), isr3(void), isr4(void), isr5(void), isr6(void), isr7(void), isr8(void), isr9(void), isr10(void), isr11(void), isr12(void), isr13(void), isr14(void), isr15(void), isr16(void), isr17(void), isr18(void), isr19(void), isr20(void), isr21(void), isr22(void), isr23(void), isr24(void), isr25(void), isr26(void), isr27(void), isr28(void), isr29(void), isr30(void), isr31(void), isr32(void), isr33(void), isr34(void), isr35(void), isr36(void), isr37(void), isr38(void), isr39(void), isr40(void), isr41(void), isr42(void), isr43(void), isr44(void), isr45(void), isr46(void),
|
||||||
|
|
||||||
and all we need to do now is replace the last comma with a semicolon using
|
and all we need to do now is replace the last comma with a semicolon using
|
||||||
_$r;_ and insert _extern void_ at the beginning of the line using _I_.
|
_$r;_ and insert _extern void_ at the beginning of the line using _I_.
|
||||||
@ -63,41 +63,41 @@ _$r;_ and insert _extern void_ at the beginning of the line using _I_.
|
|||||||
|
|
||||||
Starting with
|
Starting with
|
||||||
|
|
||||||
INTNOERR 0
|
:::nasm
|
||||||
{: .prettyprint}
|
INTNOERR 0
|
||||||
|
|
||||||
I used
|
I used
|
||||||
|
|
||||||
qayypcwINTNOERR<esc>$<ctrl>a0q
|
qayypcwINTNOERR<esc>$<ctrl>a0q
|
||||||
qsyypcwINTERR<esc>$<ctrl>a0q
|
qsyypcwINTERR<esc>$<ctrl>a0q
|
||||||
dd6@a@s@a5@s33@a
|
dd6@a@s@a5@s33@a
|
||||||
|
|
||||||
and ended up with
|
and ended up with
|
||||||
|
|
||||||
INTNOERR 0
|
:::nasm
|
||||||
INTNOERR 1
|
INTNOERR 0
|
||||||
INTNOERR 2
|
INTNOERR 1
|
||||||
INTNOERR 3
|
INTNOERR 2
|
||||||
INTNOERR 4
|
INTNOERR 3
|
||||||
INTNOERR 5
|
INTNOERR 4
|
||||||
INTNOERR 6
|
INTNOERR 5
|
||||||
INTNOERR 7
|
INTNOERR 6
|
||||||
INTERR 8
|
INTNOERR 7
|
||||||
INTNOERR 9
|
INTERR 8
|
||||||
INTERR 10
|
INTNOERR 9
|
||||||
INTERR 11
|
INTERR 10
|
||||||
INTERR 12
|
INTERR 11
|
||||||
INTERR 13
|
INTERR 12
|
||||||
INTERR 14
|
INTERR 13
|
||||||
INTNOERR 15
|
INTERR 14
|
||||||
...
|
INTNOERR 15
|
||||||
INTNOERR 45
|
...
|
||||||
INTNOERR 46
|
INTNOERR 45
|
||||||
{: .prettyprint}
|
INTNOERR 46
|
||||||
|
|
||||||
I love vim!
|
I love vim!
|
||||||
|
|
||||||
###Application
|
###Application
|
||||||
So where did I use this? I've been writing some code for handling interrupts in
|
So where did I use this? I've been writing some code for handling interrupts in
|
||||||
the os. You can find it in Git commit
|
the os. You can find it in Git commit
|
||||||
[26dd8e4c75](https://github.com/thomasloven/os5/tree/26dd8e4c7507b66e4f94bf2c4e980265c6f0a20b).
|
[26dd8e4c75](https://github.com/thomasloven/os5/tree/26dd8e4c7507b66e4f94bf2c4e980265c6f0a20b).
|
||||||
|
@ -34,7 +34,7 @@ To find out whether the device has a front facing camera or not it looks into
|
|||||||
the property list of _Springboard_-the main interface of iOS. This is located
|
the property list of _Springboard_-the main interface of iOS. This is located
|
||||||
at
|
at
|
||||||
|
|
||||||
/System/Library/CoreServices/Springboard.app/
|
/System/Library/CoreServices/Springboard.app/
|
||||||
|
|
||||||
and is called either N??AP.plist or K??AP.plist on an iPhone or an iPad
|
and is called either N??AP.plist or K??AP.plist on an iPhone or an iPad
|
||||||
respectively where ?? is a number that seems to vary with your model. In there
|
respectively where ?? is a number that seems to vary with your model. In there
|
||||||
|
@ -14,16 +14,16 @@ popular. With segmentation, the physical memory is divided into segments that
|
|||||||
work as a kind of translation table. In Protected mode, if you call an address
|
work as a kind of translation table. In Protected mode, if you call an address
|
||||||
like
|
like
|
||||||
|
|
||||||
jmp CS:AX
|
:::nasm
|
||||||
{: .prettyprint .lang-nasm}
|
jmp CS:AX
|
||||||
|
|
||||||
the processor looks into the currently loaded __Local__ or __Global Descriptor
|
the processor looks into the currently loaded __Local__ or __Global Descriptor
|
||||||
Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or
|
Table__ ( __LDT__/ __GDT__) for the entry pointed to by _CS_. This enty (or
|
||||||
__Segment Descriptor__) describes the beginning of a segment which is combined
|
__Segment Descriptor__) describes the beginning of a segment which is combined
|
||||||
with the offset in _AX_ to get the physical address;
|
with the offset in _AX_ to get the physical address;
|
||||||
|
|
||||||
physical_address = segment_descriptor_from_index(CS).base + AX;
|
:::c
|
||||||
{: .prettyprint}
|
physical_address = segment_descriptor_from_index(CS).base + AX;
|
||||||
|
|
||||||
The segment descriptor also has a limit, which in our example is the maximum
|
The segment descriptor also has a limit, which in our example is the maximum
|
||||||
value _AX_ is allowed to take. If it's higher, you get a __Segmentation Fault__
|
value _AX_ is allowed to take. If it's higher, you get a __Segmentation Fault__
|
||||||
@ -76,11 +76,11 @@ Changing the CPL is actually two different problems.
|
|||||||
|
|
||||||
Increasing the CPL is relatively easy. It can be done either through a far jump
|
Increasing the CPL is relatively easy. It can be done either through a far jump
|
||||||
|
|
||||||
JMP 0x1B:label
|
:::nasm
|
||||||
label:
|
JMP 0x1B:label
|
||||||
; The CS selector is now 0x18 | 0x3
|
label:
|
||||||
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
|
; The CS selector is now 0x18 | 0x3
|
||||||
{: .prettyprint .lang-nasm}
|
; i.e. it points to segment no 3 (3*0x8) and CPL is set to 0x3
|
||||||
|
|
||||||
or through the `IRET` instruction
|
or through the `IRET` instruction
|
||||||
|
|
||||||
@ -119,32 +119,33 @@ bottom two bits to 0x3, you will soon be in User Mode.
|
|||||||
An other (better in my opinion) option is to create a fake interrupt-pushed
|
An other (better in my opinion) option is to create a fake interrupt-pushed
|
||||||
stack and push that onto the stack before running `IRET` .
|
stack and push that onto the stack before running `IRET` .
|
||||||
|
|
||||||
// C code
|
:::c
|
||||||
struct
|
// C code
|
||||||
{
|
struct
|
||||||
uint32_t esp;
|
{
|
||||||
uint32_t ss;
|
uint32_t esp;
|
||||||
uint32_t eflags;
|
uint32_t ss;
|
||||||
uint32_t eip;
|
uint32_t eflags;
|
||||||
uint32_t cs;
|
uint32_t eip;
|
||||||
} fake_stack;
|
uint32_t cs;
|
||||||
|
} fake_stack;
|
||||||
|
|
||||||
fake_stack.esp = usermode_stack_top;
|
fake_stack.esp = usermode_stack_top;
|
||||||
fake_stack.ss = user_data_segment | 0x3;
|
fake_stack.ss = user_data_segment | 0x3;
|
||||||
fake_stack.eflags = 0;
|
fake_stack.eflags = 0;
|
||||||
fake_stack.eip = &usermode_function;
|
fake_stack.eip = &usermode_function;
|
||||||
fake_stack.cs = user_code_segment | 0x3;
|
fake_stack.cs = user_code_segment | 0x3;
|
||||||
|
|
||||||
set_all_segments(user_data_segment | 0x3);
|
|
||||||
run_iret(&fake_stack);
|
|
||||||
{: .prettyprint}
|
|
||||||
|
|
||||||
; Assembler code
|
set_all_segments(user_data_segment | 0x3);
|
||||||
run_iret:
|
run_iret(&fake_stack);
|
||||||
add esp, 0x8
|
|
||||||
iret
|
|
||||||
{: .prettyprint .lang-nasm}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:::nasm
|
||||||
|
; Assembler code
|
||||||
|
run_iret:
|
||||||
|
add esp, 0x8
|
||||||
|
iret
|
||||||
|
|
||||||
###Going back to ring0
|
###Going back to ring0
|
||||||
|
|
||||||
|
@ -53,11 +53,11 @@ hard in fact that most tutorials actually do. Here's how it's really done
|
|||||||
(source: [Intel
|
(source: [Intel
|
||||||
Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/))
|
Manuals](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html/))
|
||||||
|
|
||||||
gdt[TSS_DESCRIPTOR].base = &tss;
|
:::c
|
||||||
gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
|
gdt[TSS_DESCRIPTOR].base = &tss;
|
||||||
gdt[TSS_DESCRIPTOR].flags = 0;
|
gdt[TSS_DESCRIPTOR].limit = sizeof(tss);
|
||||||
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED;
|
gdt[TSS_DESCRIPTOR].flags = 0;
|
||||||
{: .prettyprint}
|
gdt[TSS_DESCRIPTOR].access = GDT_PRESENT | GDT_EXECUTABLE | GDT_ACCESSED;
|
||||||
|
|
||||||
What most tutorials get wrong is the limit field. The TSS is actually a memory
|
What most tutorials get wrong is the limit field. The TSS is actually a memory
|
||||||
segment, and like all memory segments it has a segment descriptor in the GDT
|
segment, and like all memory segments it has a segment descriptor in the GDT
|
||||||
|
@ -39,95 +39,96 @@ Since the return address is stored on the stack, if you were to switch
|
|||||||
stacks inside a function, when you return, you'll be somewhere else.
|
stacks inside a function, when you return, you'll be somewhere else.
|
||||||
This is a common way of making usermode threads. Ponder the following:
|
This is a common way of making usermode threads. Ponder the following:
|
||||||
|
|
||||||
void switch_thread()
|
:::c
|
||||||
{
|
void switch_thread()
|
||||||
push_all_registers();
|
{
|
||||||
switch_stack_pointer();
|
push_all_registers();
|
||||||
pop_all_registers();
|
switch_stack_pointer();
|
||||||
return;
|
pop_all_registers();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
void a()
|
|
||||||
{
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
do_something();
|
|
||||||
switch_thread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void b()
|
void a()
|
||||||
{
|
{
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
do_something_else();
|
do_something();
|
||||||
switch_thread();
|
switch_thread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void b()
|
||||||
|
{
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
do_something_else();
|
||||||
|
switch_thread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__
|
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__
|
||||||
runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
|
runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
|
||||||
running. The top of the stacks looks like:
|
running. The top of the stacks looks like:
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|switch_stack_pointer RA|
|
|switch_stack_pointer RA|
|
||||||
|all registers |
|
|all registers |
|
||||||
+----------ESP----------+ |switch_thread RA |
|
+----------ESP----------+ |switch_thread RA |
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
where `RA` means Return Address and `ESP` is where the stack pointer is
|
where `RA` means Return Address and `ESP` is where the stack pointer is
|
||||||
currently pointing.
|
currently pointing.
|
||||||
As execution of __A__ continues, the processor will `do_something()` and
|
As execution of __A__ continues, the processor will `do_something()` and
|
||||||
then call `switch_thread()`...
|
then call `switch_thread()`...
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|switch_stack_pointer RA|
|
|switch_stack_pointer RA|
|
||||||
+----------ESP----------+ |all registers |
|
+----------ESP----------+ |all registers |
|
||||||
|switch_thread RA | |switch_thread RA |
|
|switch_thread RA | |switch_thread RA |
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
`switch_thread()` pushes all registers to the stack and calls
|
`switch_thread()` pushes all registers to the stack and calls
|
||||||
`switch_stack_pointer()`
|
`switch_stack_pointer()`
|
||||||
|
|
||||||
+----------ESP----------+ +-----------------------+
|
+----------ESP----------+ +-----------------------+
|
||||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||||
|all registers | |all registers |
|
|all registers | |all registers |
|
||||||
|switch_thread RA | |switch_thread RA |
|
|switch_thread RA | |switch_thread RA |
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
`switch_stack_pointer()` performs some scheduling to find out which
|
`switch_stack_pointer()` performs some scheduling to find out which
|
||||||
thread is to run next, and then switches the stack pointer over to the
|
thread is to run next, and then switches the stack pointer over to the
|
||||||
top of __B__'s stack.
|
top of __B__'s stack.
|
||||||
|
|
||||||
+-----------------------+ +----------ESP----------+
|
+-----------------------+ +----------ESP----------+
|
||||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||||
|all registers | |all registers |
|
|all registers | |all registers |
|
||||||
|switch_thread RA | |switch_thread RA |
|
|switch_thread RA | |switch_thread RA |
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
The processor keeps on executing code, and `switch_stack_pointer()` soon
|
The processor keeps on executing code, and `switch_stack_pointer()` soon
|
||||||
returns
|
returns
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|switch_stack_pointer RA| +----------ESP----------+
|
|switch_stack_pointer RA| +----------ESP----------+
|
||||||
|all registers | |all registers |
|
|all registers | |all registers |
|
||||||
|switch_thread RA | |switch_thread RA |
|
|switch_thread RA | |switch_thread RA |
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
`switch_thread()` pops all registers and returns...
|
`switch_thread()` pops all registers and returns...
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|switch_stack_pointer RA|
|
|switch_stack_pointer RA|
|
||||||
|all registers |
|
|all registers |
|
||||||
|switch_thread RA | +----------ESP----------+
|
|switch_thread RA | +----------ESP----------+
|
||||||
|a RA | |b RA |
|
|a RA | |b RA |
|
||||||
| ... | | ... |
|
| ... | | ... |
|
||||||
{: .nopretty}
|
|
||||||
... and we're now in `b()` with all registers of __B__ loaded.
|
... and we're now in `b()` with all registers of __B__ loaded.
|
||||||
|
|
||||||
###Stacks in the kernel
|
###Stacks in the kernel
|
||||||
@ -144,38 +145,41 @@ Molloys](http://www.jamesmolloy.co.uk/tutorial_html/) or [Brandon
|
|||||||
Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
|
Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
|
||||||
have something like this to handle interrupts:
|
have something like this to handle interrupts:
|
||||||
|
|
||||||
int_stub:
|
:::nasm
|
||||||
pusha
|
int_stub:
|
||||||
|
pusha
|
||||||
xor eax, eax
|
|
||||||
mov ax, ds
|
|
||||||
push eax
|
|
||||||
|
|
||||||
mov eax, 0x10
|
|
||||||
mov ds, ax
|
|
||||||
mov es, ax
|
|
||||||
mov fs, ax
|
|
||||||
mov gs, ax
|
|
||||||
|
|
||||||
call int_handler
|
|
||||||
|
|
||||||
pop eax
|
|
||||||
mov ds, ax
|
|
||||||
mov es, ax
|
|
||||||
mov fs, ax
|
|
||||||
mov gs, ax
|
|
||||||
|
|
||||||
popa
|
|
||||||
|
|
||||||
add esp, 8
|
|
||||||
|
|
||||||
iret
|
|
||||||
{: .lang-nasm}
|
|
||||||
|
|
||||||
void int_handler(registers_t r)
|
xor eax, eax
|
||||||
{
|
mov ax, ds
|
||||||
do_stuff();
|
push eax
|
||||||
}
|
|
||||||
|
mov eax, 0x10
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
call int_handler
|
||||||
|
|
||||||
|
pop eax
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
popa
|
||||||
|
|
||||||
|
add esp, 8
|
||||||
|
|
||||||
|
iret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:::c
|
||||||
|
void int_handler(registers_t r)
|
||||||
|
{
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
|
||||||
In fact, if you've been following one of those tutorials, you probably
|
In fact, if you've been following one of those tutorials, you probably
|
||||||
have the above code twice, for some reason...
|
have the above code twice, for some reason...
|
||||||
@ -184,42 +188,45 @@ Anyway. This would take care of both pushing and poping all registers,
|
|||||||
and with only a small modification, it becomes very easy to switch the
|
and with only a small modification, it becomes very easy to switch the
|
||||||
stacks too...
|
stacks too...
|
||||||
|
|
||||||
int_stub:
|
:::nasm
|
||||||
pusha
|
int_stub:
|
||||||
|
pusha
|
||||||
xor eax, eax
|
|
||||||
mov ax, ds
|
|
||||||
push eax
|
|
||||||
|
|
||||||
mov eax 0x10
|
|
||||||
mov ds, ax
|
|
||||||
mov es, ax
|
|
||||||
mov fs, ax
|
|
||||||
mov gs, ax
|
|
||||||
|
|
||||||
push esp ;Pass stack pointer to int_handler
|
|
||||||
call int_handler
|
|
||||||
mov esp, eax ;int_handler returns a new stack pointer
|
|
||||||
|
|
||||||
pop eax
|
|
||||||
mov ds, ax
|
|
||||||
mov es, ax
|
|
||||||
mov fs, ax
|
|
||||||
mov gs, ax
|
|
||||||
|
|
||||||
popa
|
|
||||||
|
|
||||||
add esp, 8
|
|
||||||
|
|
||||||
iret
|
|
||||||
{: .lang-nasm }
|
|
||||||
|
|
||||||
registers_t *int_handler(registers_t *r)
|
xor eax, eax
|
||||||
{
|
mov ax, ds
|
||||||
do_stuff();
|
push eax
|
||||||
r = get_next_thread(r);
|
|
||||||
return r;
|
mov eax 0x10
|
||||||
}
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
push esp ;Pass stack pointer to int_handler
|
||||||
|
call int_handler
|
||||||
|
mov esp, eax ;int_handler returns a new stack pointer
|
||||||
|
|
||||||
|
pop eax
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
popa
|
||||||
|
|
||||||
|
add esp, 8
|
||||||
|
|
||||||
|
iret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:::c
|
||||||
|
registers_t *int_handler(registers_t *r)
|
||||||
|
{
|
||||||
|
do_stuff();
|
||||||
|
r = get_next_thread(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
This gives a pointer to the threads registers as input to the ISR and
|
This gives a pointer to the threads registers as input to the ISR and
|
||||||
expect a pointer to some registers in return. They may or may not be the
|
expect a pointer to some registers in return. They may or may not be the
|
||||||
@ -246,56 +253,55 @@ single data structure. So let's think about it for a while.
|
|||||||
While the thread is running we want some information stored somewhere in
|
While the thread is running we want some information stored somewhere in
|
||||||
kernel space about it.
|
kernel space about it.
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|thread information |
|
|thread information |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
{: .nopretty}
|
|
||||||
|
|
||||||
Then, when an interrupt or syscall happens, a new stack is loaded
|
Then, when an interrupt or syscall happens, a new stack is loaded
|
||||||
and some stuff is pushed onto it. If we want this near our thread
|
and some stuff is pushed onto it. If we want this near our thread
|
||||||
information it will have to go right before it, since the stack grows
|
information it will have to go right before it, since the stack grows
|
||||||
backwards.
|
backwards.
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|thread registers |
|
|thread registers |
|
||||||
|thread information |
|
|thread information |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
{: .nopretty}
|
|
||||||
|
|
||||||
Finally, we want the kernel mode stack. Well... the stack pointer is
|
Finally, we want the kernel mode stack. Well... the stack pointer is
|
||||||
right at the start of the registers now, so why not just continue the
|
right at the start of the registers now, so why not just continue the
|
||||||
stack from there?
|
stack from there?
|
||||||
|
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
| ... |
|
| ... |
|
||||||
|kernel mode stack |
|
|kernel mode stack |
|
||||||
|thread registers |
|
|thread registers |
|
||||||
|thread information |
|
|thread information |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
{: .nopretty}
|
|
||||||
|
|
||||||
###Setting this up
|
###Setting this up
|
||||||
To set this up, the thread information structure has to be set up
|
To set this up, the thread information structure has to be set up
|
||||||
something like:
|
something like:
|
||||||
|
|
||||||
struct thread_info_struct
|
:::c
|
||||||
{
|
struct thread_info_struct
|
||||||
uint8_t stack_space[KERNEL_STACK_SIZE];
|
{
|
||||||
registers_t r;
|
uint8_t stack_space[KERNEL_STACK_SIZE];
|
||||||
struct thread_data_struct thread_data;
|
registers_t r;
|
||||||
} my_thread_info;
|
struct thread_data_struct thread_data;
|
||||||
|
} my_thread_info;
|
||||||
|
|
||||||
When the thread is running in user mode, the TSS should be set up in
|
When the thread is running in user mode, the TSS should be set up in
|
||||||
such a way that the stack pointer loaded at an interrupt points to the
|
such a way that the stack pointer loaded at an interrupt points to the
|
||||||
end of the registers, i.e. the beginning of the thread data.
|
end of the registers, i.e. the beginning of the thread data.
|
||||||
|
|
||||||
TSS.esp0 = &my_thread_info.thread_data;
|
:::c
|
||||||
|
TSS.esp0 = &my_thread_info.thread_data;
|
||||||
|
|
||||||
And that's really all there is to it. Unbelievable, really, how many
|
And that's really all there is to it. Unbelievable, really, how many
|
||||||
years it took for me to figure this out.
|
years it took for me to figure this out.
|
||||||
|
|
||||||
In the process, I've found inspiration in [Rhombus by Nick
|
In the process, I've found inspiration in [Rhombus by Nick
|
||||||
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
|
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
|
||||||
[linux](http://www.linux.org).
|
[linux](http://www.linux.org).
|
||||||
|
|
||||||
###Some considerations
|
###Some considerations
|
||||||
@ -335,15 +341,16 @@ I recently learned - the hard way - that the [clang
|
|||||||
compiler](http://clang.llvm.org) does not use this calling convention
|
compiler](http://clang.llvm.org) does not use this calling convention
|
||||||
for functions which do not in turn call other functions. I.e
|
for functions which do not in turn call other functions. I.e
|
||||||
|
|
||||||
int double_integer(int a)
|
:::c
|
||||||
{
|
int double_integer(int a)
|
||||||
return 2*a;
|
{
|
||||||
}
|
return 2*a;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
double_integer(5);
|
double_integer(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
If this code is compiled with clang `double_integer` will (in some
|
If this code is compiled with clang `double_integer` will (in some
|
||||||
cases) not push `ebp` to stack.
|
cases) not push `ebp` to stack.
|
||||||
|
@ -21,11 +21,12 @@ what I did, and then I branched off the last commit I blogged about
|
|||||||
|
|
||||||
For future reference (I'll probably cheat again someday) there were the commands for this:
|
For future reference (I'll probably cheat again someday) there were the commands for this:
|
||||||
|
|
||||||
git commit -m 'Bad excuse for not checking in before'
|
:::bash
|
||||||
git checkout -b new_master OLD_COMMIT_SHA
|
$ git commit -m 'Bad excuse for not checking in before'
|
||||||
git merge --strategy=ours master
|
$ git checkout -b new_master OLD_COMMIT_SHA
|
||||||
git checkout master
|
$ git merge --strategy=ours master
|
||||||
git merge new_master
|
$ git checkout master
|
||||||
|
$ git merge new_master
|
||||||
|
|
||||||
The resulting commit is found at
|
The resulting commit is found at
|
||||||
[f74ec287db](https://github.com/thomasloven/os5/tree/f74ec287db488a7bda5
|
[f74ec287db](https://github.com/thomasloven/os5/tree/f74ec287db488a7bda5
|
||||||
|
@ -41,8 +41,8 @@ boasts expressive error messages as a feature...
|
|||||||
|
|
||||||
Anyway with clang version 3.1 you can compile i386-elf object files through
|
Anyway with clang version 3.1 you can compile i386-elf object files through
|
||||||
|
|
||||||
> clang -ccc-host-triple i386-pc-linux -c source.c -o object.o
|
:::bash
|
||||||
{: .prettyprint .lang-sh}
|
$ clang -ccc-host-triple i386-pc-linux -c source.c -o object.o
|
||||||
|
|
||||||
`-ccc-host-triple` isn't mentioned even once in the documentation of
|
`-ccc-host-triple` isn't mentioned even once in the documentation of
|
||||||
clang nor are the possible choices. `i386-elf` which is somewhat of a
|
clang nor are the possible choices. `i386-elf` which is somewhat of a
|
||||||
@ -66,8 +66,8 @@ If you run OSX clang 3.1 is installed with the
|
|||||||
current version of Xcode. Version 3.2 is installed by
|
current version of Xcode. Version 3.2 is installed by
|
||||||
[Homebrew](http://mxcl.github.com/homebrew/).
|
[Homebrew](http://mxcl.github.com/homebrew/).
|
||||||
|
|
||||||
> homebrew install llvm
|
:::bash
|
||||||
{: .prettyprint .lang-sh}
|
$ homebrew install llvm
|
||||||
|
|
||||||
Binutils
|
Binutils
|
||||||
--------
|
--------
|
||||||
@ -82,12 +82,14 @@ Instead, again, I use Homebrew.
|
|||||||
First of all, to get a working cross target binutils, the brew formula
|
First of all, to get a working cross target binutils, the brew formula
|
||||||
will have to be changed a bit
|
will have to be changed a bit
|
||||||
|
|
||||||
> brew edit binutils
|
:::bash
|
||||||
|
$ brew edit binutils
|
||||||
|
|
||||||
Change the last configure flag (` --enable-targets=x86_64-elf,arm-none-eabi,m32r`)
|
Change the last configure flag (` --enable-targets=x86_64-elf,arm-none-eabi,m32r`)
|
||||||
to `--target=i386-elf`. I also changed the `--program-prefix` to `i386-elf-`. Save the file and run
|
to `--target=i386-elf`. I also changed the `--program-prefix` to `i386-elf-`. Save the file and run
|
||||||
|
|
||||||
> brew install binutils
|
:::bash
|
||||||
|
$ brew install binutils
|
||||||
|
|
||||||
and you're good to go.
|
and you're good to go.
|
||||||
|
|
||||||
@ -122,48 +124,53 @@ a text mode (curses mode).
|
|||||||
|
|
||||||
So I went out on a whim and tried
|
So I went out on a whim and tried
|
||||||
|
|
||||||
> brew info qemu
|
:::bash
|
||||||
|
$ brew info qemu
|
||||||
|
|
||||||
By now you should know pretty much what I think of Homebrew, so the
|
By now you should know pretty much what I think of Homebrew, so the
|
||||||
results of that command pretty much sealed the deal.
|
results of that command pretty much sealed the deal.
|
||||||
|
|
||||||
Now I run my kernel in qemu through
|
Now I run my kernel in qemu through
|
||||||
|
|
||||||
> qemu-system-i386 -kernel kernel/kernel -curses
|
:::bash
|
||||||
|
$ qemu-system-i386 -kernel kernel/kernel -curses
|
||||||
|
|
||||||
Qemu also turned out to have a monitor mode which contains some of the
|
Qemu also turned out to have a monitor mode which contains some of the
|
||||||
functions I used most often in the bochs debugger, such as printing the
|
functions I used most often in the bochs debugger, such as printing the
|
||||||
memory map. Further, this could be accessed using telnet from a
|
memory map. Further, this could be accessed using telnet from a
|
||||||
different tmux pane.
|
different tmux pane.
|
||||||
|
|
||||||
#!/bin/bash
|
:::bash
|
||||||
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
|
#!/bin/bash
|
||||||
tmux select-pane -L
|
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server'
|
||||||
telnet localhost 4444
|
tmux select-pane -L
|
||||||
{: .prettyprint}
|
telnet localhost 4444
|
||||||
|
|
||||||
Finally, I also installed an i386-elf targeted version of gdb - using Homebrew, obviously, with the same trick as for binutils. Gdb is found in a different tap of homebrew, so that will have to be installed first
|
Finally, I also installed an i386-elf targeted version of gdb - using Homebrew, obviously, with the same trick as for binutils. Gdb is found in a different tap of homebrew, so that will have to be installed first
|
||||||
|
|
||||||
> brew tap homebrew/dupes
|
:::bash
|
||||||
> brew edit gdb
|
$ brew tap homebrew/dupes
|
||||||
|
$ brew edit gdb
|
||||||
|
|
||||||
Add the flag `--target=i386-elf` to the configure flags, save and
|
Add the flag `--target=i386-elf` to the configure flags, save and
|
||||||
|
|
||||||
> brew install gdb
|
:::bash
|
||||||
|
$ brew install gdb
|
||||||
|
|
||||||
This will link to `ì386-elf-gdb` and can be run in yet another tmux window.
|
This will link to `ì386-elf-gdb` and can be run in yet another tmux window.
|
||||||
|
|
||||||
#!/bin/bash
|
:::bash
|
||||||
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
|
#!/bin/bash
|
||||||
tmux select-pane -L
|
tmux split-window -h 'qemu-system-i386 -kernel kernel/kernel -curses -monitor telnet:localhost:4444,server -s -S'
|
||||||
tmux slit-window -v 'i386-elf-gdb'
|
tmux select-pane -L
|
||||||
tmux select-pane -U
|
tmux slit-window -v 'i386-elf-gdb'
|
||||||
telnet localhost 4444
|
tmux select-pane -U
|
||||||
|
telnet localhost 4444
|
||||||
|
|
||||||
Upon start, gdb will look for a file called `.gdbinit` which in my case contains
|
Upon start, gdb will look for a file called `.gdbinit` which in my case contains
|
||||||
|
|
||||||
file kernel/kernel
|
file kernel/kernel
|
||||||
target remote localhost:1234
|
target remote localhost:1234
|
||||||
|
|
||||||
Results
|
Results
|
||||||
-------
|
-------
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "Two Small Projects"
|
title: "Two Small Projects"
|
||||||
subtitle: "Audio and Arduino"
|
subtitle: "Audio and Arduino"
|
||||||
|
tags: [electronics]
|
||||||
|
|
||||||
###Breadboard Arduino
|
###Breadboard Arduino
|
||||||
I didn't really like the arduino when I first heard about it. It
|
I didn't really like the arduino when I first heard about it. It
|
||||||
|
@ -16,43 +16,43 @@ should not affect the other.
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
#include <stdio.h>
|
:::c
|
||||||
#include <unistd.h>
|
#include <stdio.h>
|
||||||
#include <sys/wait.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
int main(int argc, char **argv)
|
||||||
int variable = 1;
|
{
|
||||||
int pid = fork();
|
int variable = 1;
|
||||||
int status;
|
int pid = fork();
|
||||||
|
int status;
|
||||||
if( pid )
|
|
||||||
{
|
if( pid )
|
||||||
// This is the parent
|
{
|
||||||
printf("Parent says: %d\n", variable);
|
// This is the parent
|
||||||
variable = 2;
|
printf("Parent says: %d\n", variable);
|
||||||
printf("Parent says: %d\n", variable);
|
variable = 2;
|
||||||
waitpid(pid, &status, 0); // Let the child run
|
printf("Parent says: %d\n", variable);
|
||||||
printf("Parent says: %d\n", variable);
|
waitpid(pid, &status, 0); // Let the child run
|
||||||
} else {
|
printf("Parent says: %d\n", variable);
|
||||||
// This is the child
|
} else {
|
||||||
printf("Child says: %d\n", variable);
|
// This is the child
|
||||||
variable = 3;
|
printf("Child says: %d\n", variable);
|
||||||
printf("Child says: %d\n", variable);
|
variable = 3;
|
||||||
}
|
printf("Child says: %d\n", variable);
|
||||||
|
}
|
||||||
return 0;
|
|
||||||
}
|
return 0;
|
||||||
{: .lang-c}
|
}
|
||||||
|
|
||||||
This simple program should output (assuming the parent is run first and
|
This simple program should output (assuming the parent is run first and
|
||||||
is not interrupted):
|
is not interrupted):
|
||||||
|
|
||||||
Parent says: 1
|
Parent says: 1
|
||||||
Parent says: 2
|
Parent says: 2
|
||||||
Child says: 1
|
Child says: 1
|
||||||
Child says: 3
|
Child says: 3
|
||||||
Parent says: 2
|
Parent says: 2
|
||||||
|
|
||||||
The virtual memory of the X86 architecture allows us to switch out the
|
The virtual memory of the X86 architecture allows us to switch out the
|
||||||
entire memory space in one strike, and that allows for this behavior.
|
entire memory space in one strike, and that allows for this behavior.
|
||||||
@ -93,6 +93,7 @@ Finally, each area has a pointer to its owning process.
|
|||||||
Let's follow a memory area during part of a process' life.
|
Let's follow a memory area during part of a process' life.
|
||||||
|
|
||||||
###Setup
|
###Setup
|
||||||
|
|
||||||
{: .center .noborder}
|
{: .center .noborder}
|
||||||
|
|
||||||
In the figure above we see two processes, _A_ and _B_.
|
In the figure above we see two processes, _A_ and _B_.
|
||||||
@ -112,13 +113,15 @@ In other words, it is two memory pages long (assuming 4kb pages).
|
|||||||
|
|
||||||
The user types
|
The user types
|
||||||
|
|
||||||
> gcc hello_world.c
|
:::bash
|
||||||
|
$ gcc hello_world.c
|
||||||
|
|
||||||
into the terminal and the shell program executes the `fork` system call.
|
into the terminal and the shell program executes the `fork` system call.
|
||||||
|
|
||||||
This makes the kernel do a lot of things, one of which is create a new
|
This makes the kernel do a lot of things, one of which is create a new
|
||||||
memory map for the new process. It then clones all memory areas into the
|
memory map for the new process. It then clones all memory areas into the
|
||||||
new map.
|
new map.
|
||||||
|
|
||||||
{: .center .noborder}
|
{: .center .noborder}
|
||||||
|
|
||||||
The write flag of our area is unset and the CoW flag is set. The area is
|
The write flag of our area is unset and the CoW flag is set. The area is
|
||||||
@ -149,6 +152,7 @@ A while later, the parent process is scheduled in and it may also wish
|
|||||||
to write to the stack. This time the area is already split in two, and
|
to write to the stack. This time the area is already split in two, and
|
||||||
the required area has no copies, so it is just set as read/write and
|
the required area has no copies, so it is just set as read/write and
|
||||||
we're done.
|
we're done.
|
||||||
|
|
||||||
{: .center .noborder}
|
{: .center .noborder}
|
||||||
|
|
||||||
Actually, the parent process will probably perform a `waitpid` syscall
|
Actually, the parent process will probably perform a `waitpid` syscall
|
||||||
|
@ -6,15 +6,15 @@ tags: [osdev]
|
|||||||
System calls is the way user processes communicate to the kernel. Look
|
System calls is the way user processes communicate to the kernel. Look
|
||||||
at the following program, for example.
|
at the following program, for example.
|
||||||
|
|
||||||
#include <stdio.h>
|
:::c
|
||||||
|
#include <stdio.h>
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
int main(int argc, char **argv)
|
||||||
printf("Hello, world!");
|
{
|
||||||
|
printf("Hello, world!");
|
||||||
return 0;
|
|
||||||
}
|
return 0;
|
||||||
{: .lang-c}
|
}
|
||||||
|
|
||||||
When you call the program, even before it is started, the shell makes a
|
When you call the program, even before it is started, the shell makes a
|
||||||
couple of system calls such as `fork()` and `exec()`. The program itself
|
couple of system calls such as `fork()` and `exec()`. The program itself
|
||||||
@ -37,20 +37,21 @@ implemented it in my kernel yet, but here's how it would work.
|
|||||||
####User side
|
####User side
|
||||||
First the definition in the c library:
|
First the definition in the c library:
|
||||||
|
|
||||||
int read(int file, char *ptr, int len)
|
:::c
|
||||||
{
|
int read(int file, char *ptr, int len)
|
||||||
return _syscall_read(file, ptr, len);
|
{
|
||||||
}
|
return _syscall_read(file, ptr, len);
|
||||||
|
}
|
||||||
|
|
||||||
Simply a wrapper for an assembly function:
|
Simply a wrapper for an assembly function:
|
||||||
|
|
||||||
[global _syscall_read]
|
:::nasm
|
||||||
_syscall_read:
|
[global _syscall_read]
|
||||||
mov eax, SYSCALL_READ
|
_syscall_read:
|
||||||
int 0x80
|
mov eax, SYSCALL_READ
|
||||||
mov [syscall_error], edx
|
int 0x80
|
||||||
ret
|
mov [syscall_error], edx
|
||||||
{: .lang-nasm}
|
ret
|
||||||
|
|
||||||
This function puts an identifier for the system call in the `eax`
|
This function puts an identifier for the system call in the `eax`
|
||||||
register and then execute the system call interrupt.
|
register and then execute the system call interrupt.
|
||||||
@ -62,37 +63,39 @@ Conventions](http://wiki.osdev.org/Calling_Conventions) more carefully.
|
|||||||
|
|
||||||
Of course, this can be simplified with a macro to
|
Of course, this can be simplified with a macro to
|
||||||
|
|
||||||
[global _syscall_read]
|
:::nasm
|
||||||
DEF_SYSCALL(read, SYSCALL_READ)
|
[global _syscall_read]
|
||||||
{: .lang-nasm}
|
DEF_SYSCALL(read, SYSCALL_READ)
|
||||||
|
|
||||||
####Kernel side
|
####Kernel side
|
||||||
|
|
||||||
In the kernel, the system call is caught by the following function:
|
In the kernel, the system call is caught by the following function:
|
||||||
|
|
||||||
registers_t *syscall_handler(registers_t *r)
|
:::c
|
||||||
{
|
registers_t *syscall_handler(registers_t *r)
|
||||||
if(syscall_handlers[r->eax])
|
{
|
||||||
r = syscall_handlers[r->eax](r);
|
if(syscall_handlers[r->eax])
|
||||||
else
|
r = syscall_handlers[r->eax](r);
|
||||||
r->edx = ERR_NOSYSCALL;
|
else
|
||||||
|
r->edx = ERR_NOSYSCALL;
|
||||||
return r;
|
|
||||||
}
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
If the system call is registered correctly in the kernel (through the
|
If the system call is registered correctly in the kernel (through the
|
||||||
macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything
|
macro `KREG_SYSCALL(read, SYSCALL_READ)`), this will pass everything
|
||||||
onto the following function:
|
onto the following function:
|
||||||
|
|
||||||
KDEF_SYSCALL(read, r)
|
:::c
|
||||||
{
|
KDEF_SYSCALL(read, r)
|
||||||
process_stack stack = init_pstack();
|
{
|
||||||
|
process_stack stack = init_pstack();
|
||||||
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
|
|
||||||
|
r->eax = read((int)stack[0], (char *)stack[1], (int)stack[2]);
|
||||||
r->edx = errno;
|
r->edx = errno;
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
The `init_pstack()` macro expands to `(unitptr_t *)(r->useresp + 0x4)`
|
The `init_pstack()` macro expands to `(unitptr_t *)(r->useresp + 0x4)`
|
||||||
and this lets us read the arguments passed to the system call from where
|
and this lets us read the arguments passed to the system call from where
|
||||||
@ -100,10 +103,11 @@ they are pushed on call.
|
|||||||
|
|
||||||
Then the `read()` function has the same definition as the library version.
|
Then the `read()` function has the same definition as the library version.
|
||||||
|
|
||||||
int read(int file, char *ptr, int len)
|
:::c
|
||||||
{
|
int read(int file, char *ptr, int len)
|
||||||
...
|
{
|
||||||
}
|
...
|
||||||
|
}
|
||||||
|
|
||||||
_Spoiler alert:_ Keeping a version of `read()` (and in fact every
|
_Spoiler alert:_ Keeping a version of `read()` (and in fact every
|
||||||
syscall function) inside the kernel will turn out to have some really
|
syscall function) inside the kernel will turn out to have some really
|
||||||
|
@ -40,19 +40,19 @@ compiling newlib with that setup is rather annoying.
|
|||||||
|
|
||||||
I'm also a fan of clean makefiles. Take a look at this:
|
I'm also a fan of clean makefiles. Take a look at this:
|
||||||
|
|
||||||
VPATH := ../src
|
:::make
|
||||||
|
VPATH := ../src
|
||||||
CC := i586-pc-myos-gcc
|
|
||||||
|
CC := i586-pc-myos-gcc
|
||||||
TARGETS := $(shell find ../src -name "*.c")
|
|
||||||
TARGETS := $(notdir $(TARGETS))
|
TARGETS := $(shell find ../src -name "*.c")
|
||||||
TARGETS := $(patsubst %.c, %, $(TARGETS))
|
TARGETS := $(notdir $(TARGETS))
|
||||||
|
TARGETS := $(patsubst %.c, %, $(TARGETS))
|
||||||
all: $(TARGETS)
|
|
||||||
|
all: $(TARGETS)
|
||||||
clean:
|
|
||||||
-rm $(TARGETS) 2>/dev/null
|
clean:
|
||||||
{: .lang-make}
|
-rm $(TARGETS) 2>/dev/null
|
||||||
|
|
||||||
That's the makefile for the entire `/bin` directory in my os.
|
That's the makefile for the entire `/bin` directory in my os.
|
||||||
|
|
||||||
@ -67,13 +67,14 @@ make a formula for it. So after applying the patches described in the
|
|||||||
post (I even kept the name `i586-pc-myos` since I don't have a working
|
post (I even kept the name `i586-pc-myos` since I don't have a working
|
||||||
name for my kernel besides an iteration number...) I did
|
name for my kernel besides an iteration number...) I did
|
||||||
|
|
||||||
export TARGET=i586-pc-myos
|
:::bash
|
||||||
export PREFIX=/usr/local/Cellar/osdev/1.0
|
$ export TARGET=i586-pc-myos
|
||||||
# Configure, build and install binutils
|
$ export PREFIX=/usr/local/Cellar/osdev/1.0
|
||||||
brew link osdev
|
# Configure, build and install binutils
|
||||||
# Configure, build and install gcc and libgcc
|
$ brew link osdev
|
||||||
brew unlink osdev
|
# Configure, build and install gcc and libgcc
|
||||||
brew link osdev
|
$ brew unlink osdev
|
||||||
|
$ brew link osdev
|
||||||
|
|
||||||
And that prepared me for building newlib.
|
And that prepared me for building newlib.
|
||||||
|
|
||||||
@ -85,50 +86,52 @@ automake and autoconf since a couple of versions. More specifically,
|
|||||||
you need automake version 1.12 or earlier and autoconf version 2.64.
|
you need automake version 1.12 or earlier and autoconf version 2.64.
|
||||||
Unfortunately, those versions are not available through Homebrew, so ...
|
Unfortunately, those versions are not available through Homebrew, so ...
|
||||||
|
|
||||||
curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
|
:::bash
|
||||||
tar -zxf automake-1.12.tar.gz
|
$ curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
|
||||||
mkdir -p build-automake
|
$ tar -zxf automake-1.12.tar.gz
|
||||||
pushd build-automake
|
$ mkdir -p build-automake
|
||||||
../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
|
$ pushd build-automake
|
||||||
make all -j
|
$ ../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
|
||||||
make install
|
$ make all -j
|
||||||
popd
|
$ make install
|
||||||
curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
|
$ popd
|
||||||
tar -zxf autoconf-2.64.tar.gz
|
$ curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
|
||||||
pushd build-autoconf
|
$ tar -zxf autoconf-2.64.tar.gz
|
||||||
../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
|
$ pushd build-autoconf
|
||||||
make all -j
|
$ ../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
|
||||||
make install
|
$ make all -j
|
||||||
popd
|
$ make install
|
||||||
|
$ popd
|
||||||
brew switch automake 1.12
|
$ brew switch automake 1.12
|
||||||
brew switch autoconf 2.64
|
$ brew switch autoconf 2.64
|
||||||
|
|
||||||
Those last two lines tells Homebrew that you want to use those specific
|
Those last two lines tells Homebrew that you want to use those specific
|
||||||
versions for now.
|
versions for now.
|
||||||
|
|
||||||
Now for the neat part. I followed the wiki post and used the
|
Now for the neat part. I followed the wiki post and used the
|
||||||
[syscall interface](/blog/2013/06/System-Calls) i've described earlier
|
[syscall interface](/blog/2013/06/System-Calls) i've described earlier
|
||||||
but I also wrapped `crt0.S` and `syscalls.c` in
|
but I also wrapped `crt0.S` and `syscalls.c` in
|
||||||
|
|
||||||
#ifndef KERNEL_MODE
|
:::c
|
||||||
...
|
#ifndef KERNEL_MODE
|
||||||
#endif
|
...
|
||||||
|
#endif
|
||||||
|
|
||||||
Then I built it all through
|
Then I built it all through
|
||||||
|
|
||||||
pushd build-newlib
|
:::bash
|
||||||
../newlib/configure --target=$TARGET --prefix=$PREFIX
|
$ pushd build-newlib
|
||||||
export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
|
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
|
||||||
make -j
|
$ export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
|
||||||
make install
|
$ make -j
|
||||||
mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
|
$ make install
|
||||||
rm -rf *
|
$ mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
|
||||||
../newlib/configure --target=$TARGET --prefix=$PREFIX
|
$ rm -rf *
|
||||||
export CPPFLAGS_FOR_TARGET=
|
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
|
||||||
make -j
|
$ export CPPFLAGS_FOR_TARGET=
|
||||||
make install
|
$ make -j
|
||||||
popd
|
$ make install
|
||||||
|
$ popd
|
||||||
|
|
||||||
This gives me two versions of the newlib c library. One with all syscalls
|
This gives me two versions of the newlib c library. One with all syscalls
|
||||||
defined and one without. The latter is suitable for linking into the
|
defined and one without. The latter is suitable for linking into the
|
||||||
@ -160,24 +163,26 @@ compile the kernel using a very simple makefile.
|
|||||||
|
|
||||||
Somewhat simplified:
|
Somewhat simplified:
|
||||||
|
|
||||||
OBJECTS := $(shell find . -name "*.S")
|
:::make
|
||||||
OBJECTS += $(shell find . -name "*.c")
|
OBJECTS := $(shell find . -name "*.S")
|
||||||
OBJECTS := $(OBJECTS:%.S=%.o)
|
OBJECTS += $(shell find . -name "*.c")
|
||||||
OBJECTS := $(OBJECTS:%.c=%.o)
|
OBJECTS := $(OBJECTS:%.S=%.o)
|
||||||
|
OBJECTS := $(OBJECTS:%.c=%.o)
|
||||||
|
|
||||||
CC := i586-pc-myos-gcc
|
CC := i586-pc-myos-gcc
|
||||||
LDFLAGS := -nostdlib -T linkfile.ld
|
LDFLAGS := -nostdlib -T linkfile.ld
|
||||||
LDLIBS := -lkernel
|
LDLIBS := -lkernel
|
||||||
|
|
||||||
kernel: $(OBJECTS)
|
kernel: $(OBJECTS)
|
||||||
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
||||||
|
|
||||||
and everything else is taken care of by the default rules of gnu make,
|
and everything else is taken care of by the default rules of gnu make,
|
||||||
including preprocessing and assembling .S files.
|
including preprocessing and assembling .S files.
|
||||||
|
|
||||||
For executables running under my operating system it's even easier
|
For executables running under my operating system it's even easier
|
||||||
|
|
||||||
CC := i586-pc-myos-gcc
|
:::make
|
||||||
|
CC := i586-pc-myos-gcc
|
||||||
|
|
||||||
That's all.
|
That's all.
|
||||||
|
|
||||||
|
@ -10,23 +10,24 @@ where to find everything. It has the following structure. The
|
|||||||
[ELF specification](http://www.skyfree.org/linux/references/ELF_Format.pdf)
|
[ELF specification](http://www.skyfree.org/linux/references/ELF_Format.pdf)
|
||||||
gives an excellent description on the meaning and use of each field.
|
gives an excellent description on the meaning and use of each field.
|
||||||
|
|
||||||
typedef struct
|
:::c
|
||||||
{
|
typedef struct
|
||||||
uint8_t identity[16];
|
{
|
||||||
uint16_t type;
|
uint8_t identity[16];
|
||||||
uint16_t machine;
|
uint16_t type;
|
||||||
uint32_t version;
|
uint16_t machine;
|
||||||
uint32_t entry;
|
uint32_t version;
|
||||||
uint32_t ph_offset;
|
uint32_t entry;
|
||||||
uint32_t sh_offset;
|
uint32_t ph_offset;
|
||||||
uint32_t flags;
|
uint32_t sh_offset;
|
||||||
uint16_t header_size;
|
uint32_t flags;
|
||||||
uint16_t ph_size;
|
uint16_t header_size;
|
||||||
uint16_t ph_num;
|
uint16_t ph_size;
|
||||||
uint16_t sh_size;
|
uint16_t ph_num;
|
||||||
uint16_t sh_num;
|
uint16_t sh_size;
|
||||||
uint16_t strtab_index;
|
uint16_t sh_num;
|
||||||
}__attributes__((packed)) elf_header;
|
uint16_t strtab_index;
|
||||||
|
}__attributes__((packed)) elf_header;
|
||||||
|
|
||||||
The first thing we should do is check whether we actually got an
|
The first thing we should do is check whether we actually got an
|
||||||
executable ELF file. (In the following code, I'll assume the entire elf
|
executable ELF file. (In the following code, I'll assume the entire elf
|
||||||
@ -38,31 +39,33 @@ identity field. The first four bytes of this filed should always be
|
|||||||
`0x7F`,`'E'`,`'L'`,`'F'`. If that's correct, we can look at the `type`
|
`0x7F`,`'E'`,`'L'`,`'F'`. If that's correct, we can look at the `type`
|
||||||
field. For an executable standalone program, this should be `2`.
|
field. For an executable standalone program, this should be `2`.
|
||||||
|
|
||||||
int load_elf(uint8_t *data)
|
:::c
|
||||||
{
|
int load_elf(uint8_t *data)
|
||||||
elf_header *elf = (elf_header *)data;
|
{
|
||||||
if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
|
elf_header *elf = (elf_header *)data;
|
||||||
return -1;
|
if(is_elf(elf) != ELF_TYPE_EXECUTABLE)
|
||||||
...
|
return -1;
|
||||||
|
...
|
||||||
|
|
||||||
`is_elf` looks as follows. Note the use of `strncmp` which I can do
|
`is_elf` looks as follows. Note the use of `strncmp` which I can do
|
||||||
because I link [newlib into my kernel](/blog/2013/08/Catching-Up/).
|
because I link [newlib into my kernel](/blog/2013/08/Catching-Up/).
|
||||||
|
|
||||||
int is_elf(elf_header *elf)
|
:::c
|
||||||
{
|
int is_elf(elf_header *elf)
|
||||||
int iself = -1;
|
{
|
||||||
|
int iself = -1;
|
||||||
if((elf->identity[0] == 0x7f) && \
|
|
||||||
!strncmp((char *)&elf->identity[1], "ELF", 3))
|
if((elf->identity[0] == 0x7f) && \
|
||||||
{
|
!strncmp((char *)&elf->identity[1], "ELF", 3))
|
||||||
iself = 0;
|
{
|
||||||
}
|
iself = 0;
|
||||||
|
}
|
||||||
if(iself != -1)
|
|
||||||
iself = elf->type;
|
if(iself != -1)
|
||||||
|
iself = elf->type;
|
||||||
return iself;
|
|
||||||
}
|
return iself;
|
||||||
|
}
|
||||||
|
|
||||||
Should be pretty straight forward. Let's continue.
|
Should be pretty straight forward. Let's continue.
|
||||||
|
|
||||||
@ -70,35 +73,37 @@ For just loading a simple ELF program, we only need to look at the
|
|||||||
program headers which are located in a table at offset `ph_offset` in
|
program headers which are located in a table at offset `ph_offset` in
|
||||||
the file.
|
the file.
|
||||||
|
|
||||||
typedef struct
|
:::c
|
||||||
{
|
typedef struct
|
||||||
uint32_t type;
|
{
|
||||||
uint32_t offset;
|
uint32_t type;
|
||||||
uint32_t virtual_address;
|
uint32_t offset;
|
||||||
uint32_t physical_address;
|
uint32_t virtual_address;
|
||||||
uint32_t file_size;
|
uint32_t physical_address;
|
||||||
uint32_t mem_size;
|
uint32_t file_size;
|
||||||
uint32_t flags;
|
uint32_t mem_size;
|
||||||
uint32_t align;
|
uint32_t flags;
|
||||||
}__attributes__((packed)) elf_phead;
|
uint32_t align;
|
||||||
|
}__attributes__((packed)) elf_phead;
|
||||||
|
|
||||||
The program headers each tell us about one section of the file, and we
|
The program headers each tell us about one section of the file, and we
|
||||||
use them to find out what parts of the elf image should be loaded where
|
use them to find out what parts of the elf image should be loaded where
|
||||||
in memory. So, the next step would be to go through all program headers
|
in memory. So, the next step would be to go through all program headers
|
||||||
looking for loadable sections and load them into memory.
|
looking for loadable sections and load them into memory.
|
||||||
|
|
||||||
...
|
:::c
|
||||||
elf_phead *phead = (elf_phead)&data[elf->ph_offset];
|
...
|
||||||
uint32_t i;
|
elf_phead *phead = (elf_phead)&data[elf->ph_offset];
|
||||||
for(i = 0; i < elf->ph_num; i++)
|
uint32_t i;
|
||||||
{
|
for(i = 0; i < elf->ph_num; i++)
|
||||||
if(phead[i].type == ELF_PT_LOAD)
|
{
|
||||||
{
|
if(phead[i].type == ELF_PT_LOAD)
|
||||||
load_elf_segment(data, &phead[i]);
|
{
|
||||||
}
|
load_elf_segment(data, &phead[i]);
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
This would also be a good time to update the memory manager information
|
This would also be a good time to update the memory manager information
|
||||||
about the executable. You might want to keep track of the start and end
|
about the executable. You might want to keep track of the start and end
|
||||||
@ -106,25 +111,26 @@ of code and data for example.
|
|||||||
|
|
||||||
Anyway, `load_elf_segment()` looks like this
|
Anyway, `load_elf_segment()` looks like this
|
||||||
|
|
||||||
void load_elf_segment(uint8_t *data, elf_phead *phead)
|
:::c
|
||||||
{
|
void load_elf_segment(uint8_t *data, elf_phead *phead)
|
||||||
|
{
|
||||||
uint32_t memsize = phead->mem_size; // Size in memory
|
|
||||||
uint32_t filesize = phead->file_size; // Size in file
|
|
||||||
uint32_t mempos = phead->virtual_address; // Offset in memory
|
|
||||||
uint32_t filepos = phead->offset; // Offset in file
|
|
||||||
|
|
||||||
uint32_t flags = MM_FLAG_READ;
|
uint32_t memsize = phead->mem_size; // Size in memory
|
||||||
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE;
|
uint32_t filesize = phead->file_size; // Size in file
|
||||||
|
uint32_t mempos = phead->virtual_address; // Offset in memory
|
||||||
|
uint32_t filepos = phead->offset; // Offset in file
|
||||||
|
|
||||||
new_area(current->proc, mempos, mempos + memsize, \
|
uint32_t flags = MM_FLAG_READ;
|
||||||
flags, MM_TYPE_DATA);
|
if(phead->flags & ELF_PT_W) flags |= MM_FLAG_WRITE;
|
||||||
|
|
||||||
if(memsize == 0) return;
|
new_area(current->proc, mempos, mempos + memsize, \
|
||||||
|
flags, MM_TYPE_DATA);
|
||||||
|
|
||||||
memcpy(mempos, &data[filepos], filesize);
|
if(memsize == 0) return;
|
||||||
memset(mempos + filesize, 0, memsize - filesize);
|
|
||||||
}
|
memcpy(mempos, &data[filepos], filesize);
|
||||||
|
memset(mempos + filesize, 0, memsize - filesize);
|
||||||
|
}
|
||||||
|
|
||||||
Let's go through it.
|
Let's go through it.
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ What? But my code didn't even run!
|
|||||||
I reapplied my changes one at a time and found the line that caused the
|
I reapplied my changes one at a time and found the line that caused the
|
||||||
problem.
|
problem.
|
||||||
|
|
||||||
int i = 0;
|
:::c
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
What?? That's it? Declaring a variable?
|
What?? That's it? Declaring a variable?
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "Virtual File System 2"
|
title: "Virtual File System 2"
|
||||||
subtitle: "for real this time."
|
subtitle: "for real this time."
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
Once again, several months have passed since I wrote anything here.
|
Once again, several months have passed since I wrote anything here.
|
||||||
I also worked very little on the kernel during this time, though. So no
|
I also worked very little on the kernel during this time, though. So no
|
||||||
@ -33,25 +33,26 @@ generated on-the-fly by the kernel, network connections(?) etc.
|
|||||||
|
|
||||||
Further, I want the VFS to be independent of any disk file system, e.g.
|
Further, I want the VFS to be independent of any disk file system, e.g.
|
||||||
the VFS shouldn't have user-group-other read-write-execute tuples just
|
the VFS shouldn't have user-group-other read-write-execute tuples just
|
||||||
because it's designed with ext2 in mind. It might have those tuples -
|
because it's designed with ext2 in mind. It might have those tuples -
|
||||||
I haven't decided yet - but it it does it won't be because ext2 uses
|
I haven't decided yet - but it it does it won't be because ext2 uses
|
||||||
them. Nor will the VFS be designed with ext2 or any other disk file
|
them. Nor will the VFS be designed with ext2 or any other disk file
|
||||||
system in mind.
|
system in mind.
|
||||||
|
|
||||||
The VFS should offer the functions
|
The VFS should offer the functions
|
||||||
|
|
||||||
open() // Open or create a file
|
:::c
|
||||||
close() // Close a file
|
open() // Open or create a file
|
||||||
read() // Read data from opened file
|
close() // Close a file
|
||||||
write() // Write data to opened file
|
read() // Read data from opened file
|
||||||
move() // Move a file or directory
|
write() // Write data to opened file
|
||||||
link() // Put a file or directory in path tree
|
move() // Move a file or directory
|
||||||
unlink() // Remove a file or directory from path tree
|
link() // Put a file or directory in path tree
|
||||||
stat() // Get more info about a file or directory
|
unlink() // Remove a file or directory from path tree
|
||||||
isatty() // Returns true if the file is a terminal
|
stat() // Get more info about a file or directory
|
||||||
mkdir() // Create a directory
|
isatty() // Returns true if the file is a terminal
|
||||||
readdir() // Get a directory entry
|
mkdir() // Create a directory
|
||||||
finddir() // Find a file by name from a directory
|
readdir() // Get a directory entry
|
||||||
|
finddir() // Find a file by name from a directory
|
||||||
|
|
||||||
for all files and directories regardless of their underlying device or
|
for all files and directories regardless of their underlying device or
|
||||||
driver.
|
driver.
|
||||||
@ -96,20 +97,18 @@ Example:
|
|||||||
A good starting point for the inode structure might be some pointers to
|
A good starting point for the inode structure might be some pointers to
|
||||||
allow it to be placed in a tree, then.
|
allow it to be placed in a tree, then.
|
||||||
|
|
||||||
|
:::c
|
||||||
|
struct vfs_node_st;
|
||||||
|
typedef vfs_node_t * INODE;
|
||||||
|
|
||||||
struct vfs_node_st;
|
typedef struct vfs_node_st
|
||||||
typedef vfs_node_t * INODE;
|
{
|
||||||
|
char name[VFS_NAME_SZ];
|
||||||
typedef struct vfs_node_st
|
INODE parent;
|
||||||
{
|
INODE child;
|
||||||
char name[VFS_NAME_SZ];
|
INODE older, younger;
|
||||||
INODE parent;
|
uint32_t type;
|
||||||
INODE child;
|
} vfs_node_t;
|
||||||
INODE older, younger;
|
|
||||||
uint32_t type;
|
|
||||||
} vfs_node_t;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This does waste a bit of memory, since most inodes that are used by the
|
This does waste a bit of memory, since most inodes that are used by the
|
||||||
system won't be in the VFS tree, but four `size_t` isn't that much,
|
system won't be in the VFS tree, but four `size_t` isn't that much,
|
||||||
@ -125,113 +124,118 @@ to maintain.
|
|||||||
Then, we need a way to keep track of the driver, i.e. the functions
|
Then, we need a way to keep track of the driver, i.e. the functions
|
||||||
called to access the file. To do this, I define a new struct:
|
called to access the file. To do this, I define a new struct:
|
||||||
|
|
||||||
typedef struct vfs_driver_st
|
:::c
|
||||||
{
|
typedef struct vfs_driver_st
|
||||||
uint32_t (*open)(INODE, uint32_t);
|
{
|
||||||
uint32_t (*close)(INODE);
|
uint32_t (*open)(INODE, uint32_t);
|
||||||
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
|
uint32_t (*close)(INODE);
|
||||||
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
|
uint32_t (*read)(INODE, void *, uint32_t, uint32_t);
|
||||||
uint32_t (*link)(INODE, INODE, const char *);
|
uint32_t (*write)(INODE, void *, uint32_t, uint32_t);
|
||||||
uint32_t (*unlink)(INODE, const char *);
|
uint32_t (*link)(INODE, INODE, const char *);
|
||||||
uint32_t (*stat)(INODE, struct stat *st);
|
uint32_t (*unlink)(INODE, const char *);
|
||||||
uint32_t (*isatty)(INODE);
|
uint32_t (*stat)(INODE, struct stat *st);
|
||||||
uint32_t (*mkdir)(INODE, const char *);
|
uint32_t (*isatty)(INODE);
|
||||||
dirent_t *(*readdir)(INODE, uint32_t);
|
uint32_t (*mkdir)(INODE, const char *);
|
||||||
INODE (*finddir)(INODE, const char *);
|
dirent_t *(*readdir)(INODE, uint32_t);
|
||||||
} vfs_driver_t;
|
INODE (*finddir)(INODE, const char *);
|
||||||
|
} vfs_driver_t;
|
||||||
|
|
||||||
and add `vfs_driver_t *d` to the inode struct. I also added a length
|
and add `vfs_driver_t *d` to the inode struct. I also added a length
|
||||||
value, a void pointer for arbitrary data used by the drivers and a flags
|
value, a void pointer for arbitrary data used by the drivers and a flags
|
||||||
value - also for use by the drivers. The
|
value - also for use by the drivers. The
|
||||||
inode struct now looks like this:
|
inode struct now looks like this:
|
||||||
|
|
||||||
typedef struct vfs_node_st
|
:::c
|
||||||
{
|
typedef struct vfs_node_st
|
||||||
char name[VFS_NAME_SZ];
|
{
|
||||||
void *parent;
|
char name[VFS_NAME_SZ];
|
||||||
void *child;
|
void *parent;
|
||||||
void *older, *younger;
|
void *child;
|
||||||
uint32_t type;
|
void *older, *younger;
|
||||||
vfs_driver_t *d;
|
uint32_t type;
|
||||||
void *data;
|
vfs_driver_t *d;
|
||||||
uint32_t flags;
|
void *data;
|
||||||
uint32_t length;
|
uint32_t flags;
|
||||||
}
|
uint32_t length;
|
||||||
|
}
|
||||||
|
|
||||||
###Vfs functions
|
###Vfs functions
|
||||||
Next, I create some wrapper functions to call the driver functions.
|
Next, I create some wrapper functions to call the driver functions.
|
||||||
|
|
||||||
uint32_t vfs_open(INODE ino, uint32_t mode)
|
:::c
|
||||||
{
|
uint32_t vfs_open(INODE ino, uint32_t mode)
|
||||||
if(ino->d->open)
|
{
|
||||||
return ino->d->open(ino, mode);
|
if(ino->d->open)
|
||||||
return 0;
|
return ino->d->open(ino, mode);
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
and similar for all functions except `readdir` and `finddir` which
|
and similar for all functions except `readdir` and `finddir` which
|
||||||
contain code to handle `.` and `..` for mount roots.
|
contain code to handle `.` and `..` for mount roots.
|
||||||
|
|
||||||
|
|
||||||
dirent_t *vfs_readdir(INODE ino, uint32_t num)
|
:::c
|
||||||
{
|
dirent_t *vfs_readdir(INODE ino, uint32_t num)
|
||||||
if(ino->type & FS_MOUNT)
|
{
|
||||||
{
|
if(ino->type & FS_MOUNT)
|
||||||
if(num == 0)
|
{
|
||||||
{
|
if(num == 0)
|
||||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
{
|
||||||
ret->ino = ino;
|
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||||
strcpy(ret->name, ".");
|
ret->ino = ino;
|
||||||
return ret;
|
strcpy(ret->name, ".");
|
||||||
} else if(num == 1) {
|
return ret;
|
||||||
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
} else if(num == 1) {
|
||||||
ret->ino = ino->parent;
|
dirent_t *ret = calloc(1, sizeof(dirent_t));
|
||||||
strcpy(ret->name, "..");
|
ret->ino = ino->parent;
|
||||||
return ret;
|
strcpy(ret->name, "..");
|
||||||
}
|
return ret;
|
||||||
}
|
}
|
||||||
if(ino->d->readdir)
|
}
|
||||||
return ino->d->readdir(ino, num);
|
if(ino->d->readdir)
|
||||||
return 0;
|
return ino->d->readdir(ino, num);
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
INODE vfs_finddir(INODE ino, const char *name)
|
:::c
|
||||||
{
|
INODE vfs_finddir(INODE ino, const char *name)
|
||||||
if(ino->type & FS_MOUNT)
|
{
|
||||||
{
|
if(ino->type & FS_MOUNT)
|
||||||
if(!strcmp(name, "."))
|
{
|
||||||
{
|
if(!strcmp(name, "."))
|
||||||
return ino;
|
{
|
||||||
} else if(!strcmp(name, "..")) {
|
return ino;
|
||||||
return ino->parent;
|
} else if(!strcmp(name, "..")) {
|
||||||
}
|
return ino->parent;
|
||||||
}
|
}
|
||||||
if(ino->d->finddir)
|
}
|
||||||
return ino->d->finddir(ino, name);
|
if(ino->d->finddir)
|
||||||
if(ino->d->readdir)
|
return ino->d->finddir(ino, name);
|
||||||
{
|
if(ino->d->readdir)
|
||||||
// Backup solution
|
{
|
||||||
int num = 0;
|
// Backup solution
|
||||||
dirent_t *de;
|
int num = 0;
|
||||||
while(1)
|
dirent_t *de;
|
||||||
{
|
while(1)
|
||||||
de = vfs_readdir(ino, num);
|
{
|
||||||
if(!de)
|
de = vfs_readdir(ino, num);
|
||||||
return 0;
|
if(!de)
|
||||||
if(!strcmp(name, de->name))
|
return 0;
|
||||||
break;
|
if(!strcmp(name, de->name))
|
||||||
free(de->name);
|
break;
|
||||||
free(de);
|
free(de->name);
|
||||||
num++;
|
free(de);
|
||||||
}
|
num++;
|
||||||
INODE ret = de->ino;
|
}
|
||||||
free(de->name);
|
INODE ret = de->ino;
|
||||||
free(de);
|
free(de->name);
|
||||||
return ret;
|
free(de);
|
||||||
}
|
return ret;
|
||||||
return 0;
|
}
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Finally, I needed a function for mounting filesystems in the mount tree
|
Finally, I needed a function for mounting filesystems in the mount tree
|
||||||
and the `namei` function, which can actually be combined since they both
|
and the `namei` function, which can actually be combined since they both
|
||||||
@ -241,38 +245,39 @@ _Warning:_ Pointer-pointers ahead!
|
|||||||
|
|
||||||
First: a function for traversing the mount tree as far as possible
|
First: a function for traversing the mount tree as far as possible
|
||||||
|
|
||||||
INODE vfs_find_root(char **path)
|
:::c
|
||||||
{
|
INODE vfs_find_root(char **path)
|
||||||
// Find closest point in mount tree
|
{
|
||||||
INODE current = vfs_root;
|
// Find closest point in mount tree
|
||||||
INODE mount = current;
|
INODE current = vfs_root;
|
||||||
char *name;
|
INODE mount = current;
|
||||||
while((name = strsep(path, "/")))
|
char *name;
|
||||||
{
|
while((name = strsep(path, "/")))
|
||||||
current = current->child;
|
{
|
||||||
while(current)
|
current = current->child;
|
||||||
{
|
while(current)
|
||||||
if(!strcmp(current->name, name))
|
{
|
||||||
{
|
if(!strcmp(current->name, name))
|
||||||
mount = current;
|
{
|
||||||
break;
|
mount = current;
|
||||||
}
|
break;
|
||||||
current = current->olderyounger;
|
}
|
||||||
}
|
current = current->olderyounger;
|
||||||
if(!current)
|
}
|
||||||
{
|
if(!current)
|
||||||
if(*path)
|
{
|
||||||
{
|
if(*path)
|
||||||
*path = *path - 1;
|
{
|
||||||
*path[0] = '/';
|
*path = *path - 1;
|
||||||
}
|
*path[0] = '/';
|
||||||
*path = name;
|
}
|
||||||
break;
|
*path = name;
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (INODE)mount;
|
|
||||||
}
|
return (INODE)mount;
|
||||||
|
}
|
||||||
|
|
||||||
Pretty self explanatory. No? Well, `strsep` is a library function which
|
Pretty self explanatory. No? Well, `strsep` is a library function which
|
||||||
picks out one part of the path at a time and also advances the `path`
|
picks out one part of the path at a time and also advances the `path`
|
||||||
@ -284,69 +289,69 @@ The namei/mount function then uses this as a starting point:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
INODE vfs_namei_mount(const char *path, INODE root)
|
:::c
|
||||||
{
|
INODE vfs_namei_mount(const char *path, INODE root)
|
||||||
char *npath = strdup(path);
|
{
|
||||||
char *pth = &npath[1];
|
char *npath = strdup(path);
|
||||||
// Find closest point in mount tree
|
char *pth = &npath[1];
|
||||||
INODE current = vfs_find_root(&pth);
|
// Find closest point in mount tree
|
||||||
char *name;
|
INODE current = vfs_find_root(&pth);
|
||||||
while(current && (name = strsep(&pth, "/")))
|
char *name;
|
||||||
{
|
while(current && (name = strsep(&pth, "/")))
|
||||||
// Go through the path
|
{
|
||||||
INODE next = vfs_finddir(current, name);
|
// Go through the path
|
||||||
|
INODE next = vfs_finddir(current, name);
|
||||||
if(root)
|
|
||||||
{
|
if(root)
|
||||||
// If we want to mount someting
|
{
|
||||||
if(!next)
|
// If we want to mount someting
|
||||||
{
|
if(!next)
|
||||||
// Create last part of path if it doesn't exist
|
{
|
||||||
// But only if it is the last part.
|
// Create last part of path if it doesn't exist
|
||||||
if(pth)
|
// But only if it is the last part.
|
||||||
return 0;
|
if(pth)
|
||||||
next = calloc(1, sizeof(vfs_node_t));
|
return 0;
|
||||||
strcpy(next->name, name);
|
next = calloc(1, sizeof(vfs_node_t));
|
||||||
next->type = FS_DIRECTORY;
|
strcpy(next->name, name);
|
||||||
}
|
next->type = FS_DIRECTORY;
|
||||||
|
}
|
||||||
// Add path to mount tree
|
|
||||||
next->parent = current;
|
// Add path to mount tree
|
||||||
next->older = current->child;
|
next->parent = current;
|
||||||
current->child = next;
|
next->older = current->child;
|
||||||
}
|
current->child = next;
|
||||||
|
}
|
||||||
if(!next)
|
|
||||||
return 0;
|
if(!next)
|
||||||
if(!current->parent)
|
return 0;
|
||||||
free(current);
|
if(!current->parent)
|
||||||
|
free(current);
|
||||||
current = next;
|
|
||||||
}
|
current = next;
|
||||||
free(npath);
|
}
|
||||||
|
free(npath);
|
||||||
if(root && current->type == FS_DIRECTORY)
|
|
||||||
{
|
if(root && current->type == FS_DIRECTORY)
|
||||||
// Replace node in mount tree
|
{
|
||||||
root->parent = current->parent;
|
// Replace node in mount tree
|
||||||
if(root->parent->child == current)
|
root->parent = current->parent;
|
||||||
root->parent->child = root;
|
if(root->parent->child == current)
|
||||||
root->older = current->older;
|
root->parent->child = root;
|
||||||
if(root->older)
|
root->older = current->older;
|
||||||
root->older->younger = current;
|
if(root->older)
|
||||||
root->younger = current->younger;
|
root->older->younger = current;
|
||||||
if(root->younger)
|
root->younger = current->younger;
|
||||||
root->younger->older = current;
|
if(root->younger)
|
||||||
strcpy(root->name, current->name);
|
root->younger->older = current;
|
||||||
root->type = FS_MOUNT;
|
strcpy(root->name, current->name);
|
||||||
if(current == vfs_root)
|
root->type = FS_MOUNT;
|
||||||
vfs_root = root;
|
if(current == vfs_root)
|
||||||
|
vfs_root = root;
|
||||||
free(current);
|
|
||||||
}
|
free(current);
|
||||||
return current;
|
}
|
||||||
}
|
return current;
|
||||||
{: .lang-c}
|
}
|
||||||
|
|
||||||
Note how `pth` is changed by `vfs_find_root()` to only contain the part
|
Note how `pth` is changed by `vfs_find_root()` to only contain the part
|
||||||
of the path that wasn't found. After that, we ask each node for the next
|
of the path that wasn't found. After that, we ask each node for the next
|
||||||
@ -360,46 +365,47 @@ the final inode is returned.
|
|||||||
I also made two simple wrappers for this function:
|
I also made two simple wrappers for this function:
|
||||||
|
|
||||||
|
|
||||||
INODE vfs_namei(const char *path)
|
:::c
|
||||||
{
|
INODE vfs_namei(const char *path)
|
||||||
return vfs_namei_mount(path, 0);
|
{
|
||||||
}
|
return vfs_namei_mount(path, 0);
|
||||||
|
}
|
||||||
INODE vfs_mount(const char *path, INODE root)
|
|
||||||
{
|
INODE vfs_mount(const char *path, INODE root)
|
||||||
return vfs_namei_mount(path, root);
|
{
|
||||||
}
|
return vfs_namei_mount(path, root);
|
||||||
{: .lang-c}
|
}
|
||||||
|
|
||||||
And finally, a function for unmounting file systems:
|
And finally, a function for unmounting file systems:
|
||||||
|
|
||||||
|
|
||||||
INODE vfs_umount(const char *path)
|
:::c
|
||||||
{
|
INODE vfs_umount(const char *path)
|
||||||
char *npath = strdup(path);
|
{
|
||||||
char *pth = &npath[1];
|
char *npath = strdup(path);
|
||||||
INODE ino = vfs_find_root(&pth);
|
char *pth = &npath[1];
|
||||||
if(!ino || pth)
|
INODE ino = vfs_find_root(&pth);
|
||||||
{
|
if(!ino || pth)
|
||||||
free(npath);
|
{
|
||||||
return 0;
|
free(npath);
|
||||||
}
|
return 0;
|
||||||
if(ino->child)
|
}
|
||||||
{
|
if(ino->child)
|
||||||
free(npath);
|
{
|
||||||
return 0;
|
free(npath);
|
||||||
} else {
|
return 0;
|
||||||
// Remove node from mount tree
|
} else {
|
||||||
if(ino->parent->child == ino)
|
// Remove node from mount tree
|
||||||
ino->parent->child = ino->older;
|
if(ino->parent->child == ino)
|
||||||
if(ino->younger)
|
ino->parent->child = ino->older;
|
||||||
ino->younger->older = ino->older;
|
if(ino->younger)
|
||||||
if(ino->older)
|
ino->younger->older = ino->older;
|
||||||
ino->older->younger = ino->younger;
|
if(ino->older)
|
||||||
free(npath);
|
ino->older->younger = ino->younger;
|
||||||
return ino;
|
free(npath);
|
||||||
}
|
return ino;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
And that's it for now. A lot of code this time, but that's because I
|
And that's it for now. A lot of code this time, but that's because I
|
||||||
don't want to push my changes to github quite yet, so I can't give you a
|
don't want to push my changes to github quite yet, so I can't give you a
|
||||||
|
@ -12,17 +12,17 @@ have some basic syscall interfaces to start from.
|
|||||||
|
|
||||||
Newlib requires the following syscalls:
|
Newlib requires the following syscalls:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int close(int file)
|
int close(int file)
|
||||||
int fstat(int file, struct stat *st)
|
int fstat(int file, struct stat *st)
|
||||||
int isatty(int file)
|
int isatty(int file)
|
||||||
int link(char *old, char *new)
|
int link(char *old, char *new)
|
||||||
int lseek(int file, int ptr, int dir)
|
int lseek(int file, int ptr, int dir)
|
||||||
int open(const char *name, int flags, int mode)
|
int open(const char *name, int flags, int mode)
|
||||||
int read(int file, char *ptr, int len)
|
int read(int file, char *ptr, int len)
|
||||||
int stat(const char *file, struct stat *st)
|
int stat(const char *file, struct stat *st)
|
||||||
int unlink(char *name)
|
int unlink(char *name)
|
||||||
int write(int file, char *ptr, int len)
|
int write(int file, char *ptr, int len)
|
||||||
|
|
||||||
###open and close
|
###open and close
|
||||||
Everything starts with `open`, so let's look at that first.
|
Everything starts with `open`, so let's look at that first.
|
||||||
@ -30,79 +30,79 @@ Everything starts with `open`, so let's look at that first.
|
|||||||
In order to keep track of the files that are opened by a process, we
|
In order to keep track of the files that are opened by a process, we
|
||||||
need a new data structure, though; the _file descriptor_.
|
need a new data structure, though; the _file descriptor_.
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
INODE ino;
|
INODE ino;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
uint32_t users;
|
uint32_t users;
|
||||||
} file_desc_t;
|
} file_desc_t;
|
||||||
|
|
||||||
The file descriptor keeps track of our position in the file as well as
|
The file descriptor keeps track of our position in the file as well as
|
||||||
the mode it was opened in. File descriptors can also be shared between
|
the mode it was opened in. File descriptors can also be shared between
|
||||||
processes (after a `fork()` for example), and it therefore has a use
|
processes (after a `fork()` for example), and it therefore has a use
|
||||||
counter. Two macros are used to manipulate the use counter
|
counter. Two macros are used to manipulate the use counter
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
#define fd_get(fd) { (fd)->users++ }
|
#define fd_get(fd) { (fd)->users++ }
|
||||||
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
|
#define fd_put(fd) { (fd)->users--; if(!(fd)->users)free(fd) }
|
||||||
|
|
||||||
Each process descriptor has an array of pointers to file descriptors
|
Each process descriptor has an array of pointers to file descriptors
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
file_desc_t *fd[NUM_FILEDES];
|
file_desc_t *fd[NUM_FILEDES];
|
||||||
|
|
||||||
`open` starts by finding a free file descriptor. It then finds the file,
|
`open` starts by finding a free file descriptor. It then finds the file,
|
||||||
opens the file and returns the index of the file descriptor it used:
|
opens the file and returns the index of the file descriptor it used:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int open(const char *name, int flags, int mode)
|
int open(const char *name, int flags, int mode)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
// Find unused file descriptor
|
// Find unused file descriptor
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
int i;
|
int i;
|
||||||
for(i=0; i < NUM_FILEDES; i++)
|
for(i=0; i < NUM_FILEDES; i++)
|
||||||
{
|
{
|
||||||
if(p->fd[i])
|
if(p->fd[i])
|
||||||
continue;
|
continue;
|
||||||
fd = i;
|
fd = i;
|
||||||
p->fd[fd] = calloc(1, sizeof(file_desc_t));
|
p->fd[fd] = calloc(1, sizeof(file_desc_t));
|
||||||
fd_get(p->fd[fd]);
|
fd_get(p->fd[fd]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find file
|
// Find file
|
||||||
INODE ino = vfs_namei(name);
|
INODE ino = vfs_namei(name);
|
||||||
// Open file
|
// Open file
|
||||||
vfs_open(name, flags);
|
vfs_open(name, flags);
|
||||||
|
|
||||||
// Setup file descriptor
|
// Setup file descriptor
|
||||||
p->fd[fd]->ino = ino;
|
p->fd[fd]->ino = ino;
|
||||||
p->fd[fd]->offset = 0;
|
p->fd[fd]->offset = 0;
|
||||||
p->fd[fd]->flags = flags;
|
p->fd[fd]->flags = flags;
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
I stripped away all of the sanity checking and error handling code here.
|
I stripped away all of the sanity checking and error handling code here.
|
||||||
With that code, the function is more than twice as long.
|
With that code, the function is more than twice as long.
|
||||||
|
|
||||||
`close` is even easier:
|
`close` is even easier:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int close(int file)
|
int close(int file)
|
||||||
{
|
{
|
||||||
int retval = vfs_close(p->fd[file]->ino);
|
int retval = vfs_close(p->fd[file]->ino);
|
||||||
if(!p->fd[file]->ino->parent)
|
if(!p->fd[file]->ino->parent)
|
||||||
free(p->fd[file]->ino);
|
free(p->fd[file]->ino);
|
||||||
fd_put(p->fd[file]);
|
fd_put(p->fd[file]);
|
||||||
p->fd[file] = 0;
|
p->fd[file] = 0;
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
I always check if an inode has a parent before freeing it. If it has a
|
I always check if an inode has a parent before freeing it. If it has a
|
||||||
parent, it's part of the vfs mount tree, and should be kept around.
|
parent, it's part of the vfs mount tree, and should be kept around.
|
||||||
@ -112,83 +112,83 @@ Next, let's look at read.
|
|||||||
It's actually really simple (excluding sanity checking and error
|
It's actually really simple (excluding sanity checking and error
|
||||||
handling):
|
handling):
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int read(int file, char *ptr, int len)
|
int read(int file, char *ptr, int len)
|
||||||
{
|
{
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
INODE node = p->fd[file]->ino;
|
INODE node = p->fd[file]->ino;
|
||||||
int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
|
int ret = vfs_read(node, ptr, len, p->fd[file]->offset);
|
||||||
p->fd[file]->offset += ret;
|
p->fd[file]->offset += ret;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Write is pretty much the same:
|
Write is pretty much the same:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int write(int file, char *ptr, int len)
|
int write(int file, char *ptr, int len)
|
||||||
{
|
{
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
INODE node = p->fd[file]->ino;
|
INODE node = p->fd[file]->ino;
|
||||||
int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
|
int ret = vfs_write(node, ptr, len, p->fd[file]->offset);
|
||||||
p->fd[file]->offset += ret;
|
p->fd[file]->offset += ret;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
###stat, fstat and isatty
|
###stat, fstat and isatty
|
||||||
`fstat` and `isatty` just passes on the information to the corresponding
|
`fstat` and `isatty` just passes on the information to the corresponding
|
||||||
vfs functions:
|
vfs functions:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int fstat(int file, struct stat *st)
|
int fstat(int file, struct stat *st)
|
||||||
{
|
{
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
INODE node = p->fd[file]->ino;
|
INODE node = p->fd[file]->ino;
|
||||||
return vfs_fstat(node, st);
|
return vfs_fstat(node, st);
|
||||||
}
|
}
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int isatty(int file)
|
int isatty(int file)
|
||||||
{
|
{
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
INODE node = p->fd[file]->ino;
|
INODE node = p->fd[file]->ino;
|
||||||
return vfs_isatty(node);
|
return vfs_isatty(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
`stat` performs a `namei` lookup to get the node instead of taking it
|
`stat` performs a `namei` lookup to get the node instead of taking it
|
||||||
from the process' file descriptor table.
|
from the process' file descriptor table.
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int stat(const char *file, struct stat *st)
|
int stat(const char *file, struct stat *st)
|
||||||
{
|
{
|
||||||
INODE node = vfs_namei(file);
|
INODE node = vfs_namei(file);
|
||||||
int retval = vfs_fstat(node, st);
|
int retval = vfs_fstat(node, st);
|
||||||
if(!node->parent)
|
if(!node->parent)
|
||||||
free(node);
|
free(node);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
###lseek
|
###lseek
|
||||||
The final function I'll look at now is `lseek` which sets the current
|
The final function I'll look at now is `lseek` which sets the current
|
||||||
position in the file:
|
position in the file:
|
||||||
|
|
||||||
:::c
|
:::c
|
||||||
int lseek(int file, int ptr, int dir)
|
int lseek(int file, int ptr, int dir)
|
||||||
{
|
{
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
if(dir == SEEK_SET)
|
if(dir == SEEK_SET)
|
||||||
{
|
{
|
||||||
p->fd[file]->offset = ptr;
|
p->fd[file]->offset = ptr;
|
||||||
}
|
}
|
||||||
if(dir == SEEK_CUR)
|
if(dir == SEEK_CUR)
|
||||||
{
|
{
|
||||||
p->fd[file]->offset += ptr;
|
p->fd[file]->offset += ptr;
|
||||||
}
|
}
|
||||||
if(dir == SEEK_END)
|
if(dir == SEEK_END)
|
||||||
{
|
{
|
||||||
p->fd[file]->offset = p->fd[file]->ino->length + ptr;
|
p->fd[file]->offset = p->fd[file]->ino->length + ptr;
|
||||||
}
|
}
|
||||||
return p->fd[file]->offset;
|
return p->fd[file]->offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
I'll leave `link` and `unlink` for now, and come back to them when
|
I'll leave `link` and `unlink` for now, and come back to them when
|
||||||
I need them (i.e. I wish to implement a user writeable filesystem).
|
I need them (i.e. I wish to implement a user writeable filesystem).
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "The debug file system"
|
title: "The debug file system"
|
||||||
subtitle: "It's still in the kernel!"
|
subtitle: "It's still in the kernel!"
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
This far in my vfs rewrite, I've made a [vfs mount tree](/blog/2013/12/Virtual-File-System2/) and written some [file operation syscalls](/blog/2013/12/VFS-syscalls/). Now it's time to actually use this, by writing a filesystem that can be mounted and then manipulated through the syscall interface.
|
This far in my vfs rewrite, I've made a [vfs mount tree](/blog/2013/12/Virtual-File-System2/) and written some [file operation syscalls](/blog/2013/12/VFS-syscalls/). Now it's time to actually use this, by writing a filesystem that can be mounted and then manipulated through the syscall interface.
|
||||||
|
|
||||||
@ -15,31 +15,34 @@ Three functions are needed for newlib `fprintf` to be able to write to
|
|||||||
it: `stat`, `isatty` and `write`. Those are all rather simple, since
|
it: `stat`, `isatty` and `write`. Those are all rather simple, since
|
||||||
they won't need to keep track of which file we're referencing.
|
they won't need to keep track of which file we're referencing.
|
||||||
|
|
||||||
uint32_t debug_stat(INODE node, struct stat *st)
|
:::c
|
||||||
{
|
uint32_t debug_stat(INODE node, struct stat *st)
|
||||||
memset(st, 0, sizeof(struct stat));
|
{
|
||||||
st->st_mode = S_IFCHR;
|
memset(st, 0, sizeof(struct stat));
|
||||||
return 0;
|
st->st_mode = S_IFCHR;
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
I don't care much about the stat for the debug file. Maybe I'll add some
|
I don't care much about the stat for the debug file. Maybe I'll add some
|
||||||
creation time or so later...
|
creation time or so later...
|
||||||
|
|
||||||
uint32_t debug_isatty(INODE node)
|
:::c
|
||||||
{
|
uint32_t debug_isatty(INODE node)
|
||||||
return 1;
|
{
|
||||||
}
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
The debug output is a terminal.
|
The debug output is a terminal.
|
||||||
|
|
||||||
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
:::c
|
||||||
{
|
uint32_t debug_write(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
||||||
char *buf = calloc(size + 1, 1);
|
{
|
||||||
memcpy(buf, buffer, size);
|
char *buf = calloc(size + 1, 1);
|
||||||
kdbg_puts(buf);
|
memcpy(buf, buffer, size);
|
||||||
free(buf);
|
kdbg_puts(buf);
|
||||||
return size;
|
free(buf);
|
||||||
}
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
`kdbg_puts` is a function I wrote
|
`kdbg_puts` is a function I wrote
|
||||||
[a long time ago](/blog/2012/06/Kernel-Debug-Functions/) which prints a
|
[a long time ago](/blog/2012/06/Kernel-Debug-Functions/) which prints a
|
||||||
@ -48,48 +51,53 @@ data is null-terminated.
|
|||||||
|
|
||||||
With this, I can define a driver for the "debug device":
|
With this, I can define a driver for the "debug device":
|
||||||
|
|
||||||
vfs_driver_t debug_driver =
|
:::c
|
||||||
{
|
vfs_driver_t debug_driver =
|
||||||
0, // open
|
{
|
||||||
0, // close
|
0, // open
|
||||||
0, // read
|
0, // close
|
||||||
debug_write, // write
|
0, // read
|
||||||
0, // link
|
debug_write, // write
|
||||||
0, // unlink
|
0, // link
|
||||||
debug_stat, // stat
|
0, // unlink
|
||||||
debug_isatty, // isatty
|
debug_stat, // stat
|
||||||
0, // mkdir
|
debug_isatty, // isatty
|
||||||
0, // readdir
|
0, // mkdir
|
||||||
0 // finddir
|
0, // readdir
|
||||||
};
|
0 // finddir
|
||||||
|
};
|
||||||
|
|
||||||
And then write a function to setup the device:
|
And then write a function to setup the device:
|
||||||
|
|
||||||
INODE debug_dev_init()
|
:::c
|
||||||
{
|
INODE debug_dev_init()
|
||||||
INODE node = calloc(1, sizeof(vfs_node_t));
|
{
|
||||||
strcpy(node->name, "debug");
|
INODE node = calloc(1, sizeof(vfs_node_t));
|
||||||
node->d = &debug_driver;
|
strcpy(node->name, "debug");
|
||||||
node->type = FS_CHARDEV;
|
node->d = &debug_driver;
|
||||||
return node;
|
node->type = FS_CHARDEV;
|
||||||
}
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
Then, to activate it, all I need to do is add
|
Then, to activate it, all I need to do is add
|
||||||
|
|
||||||
vfs_init();
|
:::c
|
||||||
vfs_mount("/", debug_dev_init());
|
vfs_init();
|
||||||
|
vfs_mount("/", debug_dev_init());
|
||||||
|
|
||||||
in my kernel boot code. After that I can use the standard library
|
in my kernel boot code. After that I can use the standard library
|
||||||
functions:
|
functions:
|
||||||
|
|
||||||
FILE *dbg = fopen("/", "w");
|
:::c
|
||||||
fprintf(dbg, "Hello, world!\n");
|
FILE *dbg = fopen("/", "w");
|
||||||
|
fprintf(dbg, "Hello, world!\n");
|
||||||
|
|
||||||
or even:
|
or even:
|
||||||
|
|
||||||
fopen("/", "r");
|
:::c
|
||||||
fopen("/", "w");
|
fopen("/", "r");
|
||||||
printf("Hello, world!\n");
|
fopen("/", "w");
|
||||||
|
printf("Hello, world!\n");
|
||||||
|
|
||||||
That's it for this time. Next time, we'll do some piping!
|
That's it for this time. Next time, we'll do some piping!
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "Pipes"
|
title: "Pipes"
|
||||||
subtitle: "... and keyboard."
|
subtitle: "... and keyboard."
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
In most unix-like systems, pipes can be used to make processes
|
In most unix-like systems, pipes can be used to make processes
|
||||||
communicate with each other. To the processes the pipe looks just like
|
communicate with each other. To the processes the pipe looks just like
|
||||||
@ -33,51 +33,52 @@ Consumer/Reader:
|
|||||||
|
|
||||||
In code this translates to
|
In code this translates to
|
||||||
|
|
||||||
uint32_t pipe_write(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
:::c
|
||||||
{
|
uint32_t pipe_write(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
||||||
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
{
|
||||||
char *buf = (char *)buffer;
|
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
||||||
uint32_t bytes_written = 0;
|
char *buf = (char *)buffer;
|
||||||
while(bytes_written < size)
|
uint32_t bytes_written = 0;
|
||||||
{
|
while(bytes_written < size)
|
||||||
while((pipe->write_pos - pipe->read_pos) < pipe->size && bytes_written < size)
|
{
|
||||||
{
|
while((pipe->write_pos - pipe->read_pos) < pipe->size && bytes_written < size)
|
||||||
pipe->buffer[pipe->write_pos % pipe->size] = buf[bytes_written];
|
{
|
||||||
bytes_written++;
|
pipe->buffer[pipe->write_pos % pipe->size] = buf[bytes_written];
|
||||||
pipe->write_pos++;
|
bytes_written++;
|
||||||
}
|
pipe->write_pos++;
|
||||||
scheduler_wake(&pipe->waiting);
|
}
|
||||||
if(bytes_written < size)
|
scheduler_wake(&pipe->waiting);
|
||||||
{
|
if(bytes_written < size)
|
||||||
scheduler_sleep(current, &pipe->waiting);
|
{
|
||||||
schedule();
|
scheduler_sleep(current, &pipe->waiting);
|
||||||
}
|
schedule();
|
||||||
}
|
}
|
||||||
return bytes_written;
|
}
|
||||||
}
|
return bytes_written;
|
||||||
|
}
|
||||||
uint32_t pipe_read(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
|
||||||
{
|
uint32_t pipe_read(INODE ino, void *buffer, uint32_t size, uint32_t offset)
|
||||||
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
{
|
||||||
char *buf = (char *)buffer;
|
vfs_pipe_t *pipe = (vfs_pipe_t *)ino->data;
|
||||||
uint32_t bytes_read = 0;
|
char *buf = (char *)buffer;
|
||||||
while(bytes_read == 0)
|
uint32_t bytes_read = 0;
|
||||||
{
|
while(bytes_read == 0)
|
||||||
while((pipe->write_pos - pipe->read_pos) > 0 && bytes_read < size)
|
{
|
||||||
{
|
while((pipe->write_pos - pipe->read_pos) > 0 && bytes_read < size)
|
||||||
buf[bytes_read] = pipe->buffer[pipe->read_pos % pipe->size];
|
{
|
||||||
bytes_read++;
|
buf[bytes_read] = pipe->buffer[pipe->read_pos % pipe->size];
|
||||||
pipe->read_pos++;
|
bytes_read++;
|
||||||
}
|
pipe->read_pos++;
|
||||||
scheduler_wake(&pipe->waiting);
|
}
|
||||||
if(bytes_read == 0)
|
scheduler_wake(&pipe->waiting);
|
||||||
{
|
if(bytes_read == 0)
|
||||||
scheduler_sleep(current, &pipe->waiting);
|
{
|
||||||
schedule();
|
scheduler_sleep(current, &pipe->waiting);
|
||||||
}
|
schedule();
|
||||||
}
|
}
|
||||||
return bytes_read;
|
}
|
||||||
}
|
return bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
Of course there should also be:
|
Of course there should also be:
|
||||||
|
|
||||||
@ -95,87 +96,91 @@ two separate inodes - one that can be written to and one that can be
|
|||||||
read from. The `data` field of the `vfs_node_st` struct of the two
|
read from. The `data` field of the `vfs_node_st` struct of the two
|
||||||
inodes point to the same pipe struct.
|
inodes point to the same pipe struct.
|
||||||
|
|
||||||
typedef struct vfs_pipe
|
:::c
|
||||||
{
|
typedef struct vfs_pipe
|
||||||
char *buffer;
|
{
|
||||||
uint32_t size;
|
char *buffer;
|
||||||
uint32_t read_pos;
|
uint32_t size;
|
||||||
uint32_t write_pos;
|
uint32_t read_pos;
|
||||||
uint32_t readers;
|
uint32_t write_pos;
|
||||||
uint32_t writers;
|
uint32_t readers;
|
||||||
semaphore_t semaphore;
|
uint32_t writers;
|
||||||
list_head_t waiting;
|
semaphore_t semaphore;
|
||||||
} vfs_pipe_t;
|
list_head_t waiting;
|
||||||
|
} vfs_pipe_t;
|
||||||
|
|
||||||
The `readers` and `writers` fields are incremented or decremented when the read or
|
The `readers` and `writers` fields are incremented or decremented when the read or
|
||||||
write end respectively are opened or closed... respectively...
|
write end respectively are opened or closed... respectively...
|
||||||
|
|
||||||
Creating a new pipe (somewhat simplified):
|
Creating a new pipe (somewhat simplified):
|
||||||
|
|
||||||
uint32_t new_pipe(uint32_t size, INODE *nodes)
|
:::c
|
||||||
{
|
uint32_t new_pipe(uint32_t size, INODE *nodes)
|
||||||
vfs_pipe_t *pipe = calloc(1, sizeof(vfs_pipe_t));
|
{
|
||||||
pipe->buffer = malloc(size);
|
vfs_pipe_t *pipe = calloc(1, sizeof(vfs_pipe_t));
|
||||||
pipe->size = size;
|
pipe->buffer = malloc(size);
|
||||||
|
pipe->size = size;
|
||||||
|
|
||||||
nodes[0] = calloc(1, sizeof(vfs_node_t));
|
nodes[0] = calloc(1, sizeof(vfs_node_t));
|
||||||
nodes[0]->d = &pipe_driver;
|
nodes[0]->d = &pipe_driver;
|
||||||
nodes[0]->data = pipe;
|
nodes[0]->data = pipe;
|
||||||
nodes[0]->flags = PIPE_READ;
|
nodes[0]->flags = PIPE_READ;
|
||||||
|
|
||||||
nodes[1] = calloc(1, sizeof(vfs_node_t));
|
nodes[1] = calloc(1, sizeof(vfs_node_t));
|
||||||
nodes[1]->d = &pipe_driver;
|
nodes[1]->d = &pipe_driver;
|
||||||
nodes[1]->data = pipe;
|
nodes[1]->data = pipe;
|
||||||
nodes[1]->flags = PIPE_WRITE;
|
nodes[1]->flags = PIPE_WRITE;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
###Using pipes
|
###Using pipes
|
||||||
When starting up the keyboard driver, a pipe is created and connected to
|
When starting up the keyboard driver, a pipe is created and connected to
|
||||||
`stdin` - the first file descriptor - of the current process.
|
`stdin` - the first file descriptor - of the current process.
|
||||||
|
|
||||||
void keyboard_init()
|
:::c
|
||||||
{
|
void keyboard_init()
|
||||||
INODE tmp[2];
|
{
|
||||||
new_pipe(1024, tmp);
|
INODE tmp[2];
|
||||||
|
new_pipe(1024, tmp);
|
||||||
|
|
||||||
keyboard_pipe = tmp[1];
|
keyboard_pipe = tmp[1];
|
||||||
vfs_open(keyboard_pipe, O_WRONLY);
|
vfs_open(keyboard_pipe, O_WRONLY);
|
||||||
|
|
||||||
process_t *p = current->proc;
|
process_t *p = current->proc;
|
||||||
p->fd[0] = calloc(1, sizeof(file_desc_t));
|
p->fd[0] = calloc(1, sizeof(file_desc_t));
|
||||||
fd_get(p->fd[0]);
|
fd_get(p->fd[0]);
|
||||||
p->fd[0]->ino = tmp[0];
|
p->fd[0]->ino = tmp[0];
|
||||||
p->fd[0]->flags = O_RDONLY;
|
p->fd[0]->flags = O_RDONLY;
|
||||||
vfs_open(tmp[0], O_RDONLY);
|
vfs_open(tmp[0], O_RDONLY);
|
||||||
|
|
||||||
register_int_handler(IRQ2INT(IRQ_KBD), keyboard_handler);
|
register_int_handler(IRQ2INT(IRQ_KBD), keyboard_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
The keyboard handler (based off
|
The keyboard handler (based off
|
||||||
[Brandon Friesen's tutorial](http://www.osdever.net/bkerndev/Docs/keyboard.htm)
|
[Brandon Friesen's tutorial](http://www.osdever.net/bkerndev/Docs/keyboard.htm)
|
||||||
writes each decoded key to the pipe:
|
writes each decoded key to the pipe:
|
||||||
|
|
||||||
registers_t *keyboard_handler(registers_t *r)
|
:::c
|
||||||
{
|
registers_t *keyboard_handler(registers_t *r)
|
||||||
char code[2];
|
{
|
||||||
|
char code[2];
|
||||||
[...]
|
|
||||||
|
[...]
|
||||||
while(inb(KBD_STATUS_PORT) & 0x2);
|
|
||||||
scancode = inb(KBD_DATA_PORT);
|
while(inb(KBD_STATUS_PORT) & 0x2);
|
||||||
code[0] = keyboard_decode(scancode);
|
scancode = inb(KBD_DATA_PORT);
|
||||||
code[1] = '\0';
|
code[0] = keyboard_decode(scancode);
|
||||||
if(code[0])
|
code[1] = '\0';
|
||||||
{
|
if(code[0])
|
||||||
vfs_write(keyboard_pipe, (char *)code, 1, 0);
|
{
|
||||||
}
|
vfs_write(keyboard_pipe, (char *)code, 1, 0);
|
||||||
|
}
|
||||||
[...]
|
|
||||||
|
[...]
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
At a later stage, I'll probably make the keyboard driver in the kernel
|
At a later stage, I'll probably make the keyboard driver in the kernel
|
||||||
just pass the scancodes to the pipe and have the decoding done by
|
just pass the scancodes to the pipe and have the decoding done by
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "TAR Filesystem"
|
title: "TAR Filesystem"
|
||||||
subtitle: "Almost a useful system"
|
subtitle: "Almost a useful system"
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
It's finally time to implement an actuall filesystem, and all the hard
|
It's finally time to implement an actuall filesystem, and all the hard
|
||||||
work with the VFS framework will pay off.
|
work with the VFS framework will pay off.
|
||||||
@ -19,25 +19,26 @@ is preceded by a data block in human-readable format.
|
|||||||
|
|
||||||
In the POSIX standard of 1988, the data block has the following format:
|
In the POSIX standard of 1988, the data block has the following format:
|
||||||
|
|
||||||
typedef struct
|
:::c
|
||||||
{
|
typedef struct
|
||||||
unsigned char name[100];
|
{
|
||||||
unsigned char mode[8];
|
unsigned char name[100];
|
||||||
unsigned char uid[8];
|
unsigned char mode[8];
|
||||||
unsigned char gid[8];
|
unsigned char uid[8];
|
||||||
unsigned char size[12];
|
unsigned char gid[8];
|
||||||
unsigned char mtime[12];
|
unsigned char size[12];
|
||||||
unsigned char checksum[8];
|
unsigned char mtime[12];
|
||||||
unsigned char type[1];
|
unsigned char checksum[8];
|
||||||
unsigned char linkname[100];
|
unsigned char type[1];
|
||||||
unsigned char tar_indicator[6];
|
unsigned char linkname[100];
|
||||||
unsigned char tar_version[2];
|
unsigned char tar_indicator[6];
|
||||||
unsigned char owner[32];
|
unsigned char tar_version[2];
|
||||||
unsigned char group[32];
|
unsigned char owner[32];
|
||||||
unsigned char device_major[8];
|
unsigned char group[32];
|
||||||
unsigned char device_minor[8];
|
unsigned char device_major[8];
|
||||||
unsigned char prefix[155];
|
unsigned char device_minor[8];
|
||||||
}__attribute__((packed)) tar_header_t;
|
unsigned char prefix[155];
|
||||||
|
}__attribute__((packed)) tar_header_t;
|
||||||
|
|
||||||
The data block is 500 bytes long, but the data starts 512 (one standard
|
The data block is 500 bytes long, but the data starts 512 (one standard
|
||||||
disk sector) bytes after the start of the data block
|
disk sector) bytes after the start of the data block
|
||||||
@ -56,52 +57,53 @@ are loaded into ram and you might want to access them randomly.
|
|||||||
|
|
||||||
Instead, I reshape the file list into a file tree:
|
Instead, I reshape the file list into a file tree:
|
||||||
|
|
||||||
tree_t *build_tar_tree(tar_header_t *tar)
|
:::c
|
||||||
{
|
tree_t *build_tar_tree(tar_header_t *tar)
|
||||||
...
|
{
|
||||||
while(tar->name[0])
|
...
|
||||||
{
|
while(tar->name[0])
|
||||||
tartree_add_node(tree, tar, (char *)&tar->name);
|
{
|
||||||
uint32_t size;
|
tartree_add_node(tree, tar, (char *)&tar->name);
|
||||||
sscanf((char *)&tar->size, "%o", &size);
|
uint32_t size;
|
||||||
tar = (tar_header_t *)((size_t)tar + size + 512);
|
sscanf((char *)&tar->size, "%o", &size);
|
||||||
if((size_t)tar % 512)
|
tar = (tar_header_t *)((size_t)tar + size + 512);
|
||||||
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%512))
|
if((size_t)tar % 512)
|
||||||
}
|
tar = (tar_header_t *)((uint32_t)tar + 512 - ((uint32_t)tar%512))
|
||||||
...
|
}
|
||||||
}
|
...
|
||||||
|
}
|
||||||
void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
|
|
||||||
{
|
void tartree_add_node(tree_t *tree, tar_header_t *tar, char *path)
|
||||||
...
|
{
|
||||||
tree_node_t *node = tree->root;
|
...
|
||||||
char *p;
|
tree_node_t *node = tree->root;
|
||||||
for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
|
char *p;
|
||||||
{
|
for(p = strtok(path, "/"); p; p = strtok(NULL, "/"))
|
||||||
int found = 0;
|
{
|
||||||
list_t *l;
|
int found = 0;
|
||||||
for_each_in_list(&node->children)
|
list_t *l;
|
||||||
{
|
for_each_in_list(&node->children)
|
||||||
...
|
{
|
||||||
if(!strcmp(entry->name, p))
|
...
|
||||||
{
|
if(!strcmp(entry->name, p))
|
||||||
found = 1;
|
{
|
||||||
node = tn;
|
found = 1;
|
||||||
break;
|
node = tn;
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
if(!found)
|
}
|
||||||
{
|
if(!found)
|
||||||
...
|
{
|
||||||
tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
|
...
|
||||||
n->name = strdup(p);
|
tarfs_entry_t *n = malloc(sizeof(tar_entry_t));
|
||||||
n->tar = tar;
|
n->name = strdup(p);
|
||||||
...
|
n->tar = tar;
|
||||||
tree_make_child(node, new);
|
...
|
||||||
node = new;
|
tree_make_child(node, new);
|
||||||
}
|
node = new;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Note that this assumes that the files and directories of the tar archive
|
Note that this assumes that the files and directories of the tar archive
|
||||||
are in top-down order, e.g. that `/bin` is before `/bin/echo`, otherwise
|
are in top-down order, e.g. that `/bin` is before `/bin/echo`, otherwise
|
||||||
@ -112,24 +114,26 @@ strange.
|
|||||||
The tarfs driver makes use of the data field in the vfs node to store
|
The tarfs driver makes use of the data field in the vfs node to store
|
||||||
the tar tree.
|
the tar tree.
|
||||||
|
|
||||||
INODE tarfs_init(tar_header_t *tar)
|
:::c
|
||||||
{
|
INODE tarfs_init(tar_header_t *tar)
|
||||||
vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
|
{
|
||||||
strcpy(node->name, "tarfs");
|
vfs_node_t *node = calloc(1, sizeof(vfs_node_t));
|
||||||
node->d = &tarfs_driver;
|
strcpy(node->name, "tarfs");
|
||||||
node->type = FS_DIRECTORY;
|
node->d = &tarfs_driver;
|
||||||
|
node->type = FS_DIRECTORY;
|
||||||
tree_t *tar_tree = build_tar_tree(tar);
|
|
||||||
node->data = tar_tree->root;
|
tree_t *tar_tree = build_tar_tree(tar);
|
||||||
free(tar_tree);
|
node->data = tar_tree->root;
|
||||||
|
free(tar_tree);
|
||||||
return node;
|
|
||||||
}
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
I then add the following to my kernel initialization function:
|
I then add the following to my kernel initialization function:
|
||||||
|
|
||||||
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start);
|
:::c
|
||||||
vfs_mount("/", tarfs_init(tarfs_location));
|
tar_header_t *tarfs_location = assert_higher((tar_header_t *)mods[0].mod_start);
|
||||||
|
vfs_mount("/", tarfs_init(tarfs_location));
|
||||||
|
|
||||||
where `mods` is the multiboot modules table as loaded by grub.
|
where `mods` is the multiboot modules table as loaded by grub.
|
||||||
|
|
||||||
@ -137,30 +141,31 @@ where `mods` is the multiboot modules table as loaded by grub.
|
|||||||
Finally, the tarfs driver functions. For now I only need to implement
|
Finally, the tarfs driver functions. For now I only need to implement
|
||||||
`read()` and `finddir()`.
|
`read()` and `finddir()`.
|
||||||
|
|
||||||
INODE tar_finddir(INODE dir, const char *name)
|
:::c
|
||||||
{
|
INODE tar_finddir(INODE dir, const char *name)
|
||||||
tree_node_t *tn = (tree_node_t *)dir->data;
|
{
|
||||||
list_t *l;
|
tree_node_t *tn = (tree_node_t *)dir->data;
|
||||||
for_each_in_list(&tn->children, l)
|
list_t *l;
|
||||||
{
|
for_each_in_list(&tn->children, l)
|
||||||
tree_node_t *cn = list_entry(l, tree_node_t, siblings);
|
{
|
||||||
...
|
tree_node_t *cn = list_entry(l, tree_node_t, siblings);
|
||||||
if(!strcmp(entry->name, name)
|
...
|
||||||
{
|
if(!strcmp(entry->name, name)
|
||||||
INODE node = calloc(1, sizeof(vfs_node_t));
|
{
|
||||||
strcpy(node->name, entry->name);
|
INODE node = calloc(1, sizeof(vfs_node_t));
|
||||||
node->d = &tarfs_driver;
|
strcpy(node->name, entry->name);
|
||||||
node->data = (void *)cn;
|
node->d = &tarfs_driver;
|
||||||
sscanf((char *)&entry->tar->size, "%o", &node->length);
|
node->data = (void *)cn;
|
||||||
if(entry->tar->type[0] == TAR_TYPE_DIR)
|
sscanf((char *)&entry->tar->size, "%o", &node->length);
|
||||||
node->type = FS_DIRECTORY;
|
if(entry->tar->type[0] == TAR_TYPE_DIR)
|
||||||
else
|
node->type = FS_DIRECTORY;
|
||||||
node->type = FS_FILE;
|
else
|
||||||
return node;
|
node->type = FS_FILE;
|
||||||
}
|
return node;
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
`Finddir` allocates space for a new inode for each file that is searched
|
`Finddir` allocates space for a new inode for each file that is searched
|
||||||
for. It's up to the caller to free the inode when it's done with it.
|
for. It's up to the caller to free the inode when it's done with it.
|
||||||
@ -170,39 +175,42 @@ it at a few places... Someday I will clean up all my memory leaks.
|
|||||||
Anyway, `finddir` also finds the right node in the tarfs tree and puts
|
Anyway, `finddir` also finds the right node in the tarfs tree and puts
|
||||||
it in the `data` field of the inode.
|
it in the `data` field of the inode.
|
||||||
|
|
||||||
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
:::c
|
||||||
{
|
uint32_t read_tar(INODE node, void *buffer, uint32_t size, uint32_t offset)
|
||||||
tree_node_t *tn = (tree_node_t *)node->data;
|
{
|
||||||
tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
|
tree_node_t *tn = (tree_node_t *)node->data;
|
||||||
tar_header_t *tar = te->tar;
|
tarfs_entry_t *te = (tarfs_entry_t *)tn->item;
|
||||||
|
tar_header_t *tar = te->tar;
|
||||||
uint32_t tsz;
|
|
||||||
sscanf((char *)&tar->size, "%o", &tsz);
|
uint32_t tsz;
|
||||||
if(offset > tsz) return EOF;
|
sscanf((char *)&tar->size, "%o", &tsz);
|
||||||
|
if(offset > tsz) return EOF;
|
||||||
if((size + offset) > tsz) size = tsz - offset;
|
|
||||||
|
if((size + offset) > tsz) size = tsz - offset;
|
||||||
offset = offset + (uint32_t)tar + 512;
|
|
||||||
memcpy(buffer, (void *)offset, size);
|
offset = offset + (uint32_t)tar + 512;
|
||||||
if(size == tsz - offset)
|
memcpy(buffer, (void *)offset, size);
|
||||||
((char *)buffer)[size] = EOF;
|
if(size == tsz - offset)
|
||||||
|
((char *)buffer)[size] = EOF;
|
||||||
return size;
|
|
||||||
}
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
###Using it
|
###Using it
|
||||||
Now, all I need to do in order to make read-only files accessible to my
|
Now, all I need to do in order to make read-only files accessible to my
|
||||||
kernel is put them in a directory and run
|
kernel is put them in a directory and run
|
||||||
|
|
||||||
$ tar -cf tarfs.tar tarfs/*
|
:::bash
|
||||||
|
$ tar -cf tarfs.tar tarfs/*
|
||||||
|
|
||||||
and then make sure `tarfs.tar` is loaded as a multiboot module by qemu
|
and then make sure `tarfs.tar` is loaded as a multiboot module by qemu
|
||||||
|
|
||||||
$ qemu-system-i386 -kernel kernel -initrd tarfs.tar
|
:::bash
|
||||||
|
$ qemu-system-i386 -kernel kernel -initrd tarfs.tar
|
||||||
|
|
||||||
or by adding a line to the grub `menu.lst` file.
|
or by adding a line to the grub `menu.lst` file.
|
||||||
|
|
||||||
module /boot/tarfs.tar
|
module /boot/tarfs.tar
|
||||||
|
|
||||||
###Final note
|
###Final note
|
||||||
While writing this post, I got back to polishing this code and added
|
While writing this post, I got back to polishing this code and added
|
||||||
|
@ -5,17 +5,18 @@ tags: [osdev]
|
|||||||
|
|
||||||
I've described my [syscall interface](/blog/2013/06/System-Calls/) previously. I've also described
|
I've described my [syscall interface](/blog/2013/06/System-Calls/) previously. I've also described
|
||||||
the [file-related syscalls](/blog/2013/12/VFS-syscalls/). In order to build [newlib](http://wiki.osdev.org/Porting_Newlib), some more
|
the [file-related syscalls](/blog/2013/12/VFS-syscalls/). In order to build [newlib](http://wiki.osdev.org/Porting_Newlib), some more
|
||||||
syscalls are required.
|
syscalls are required.
|
||||||
|
|
||||||
Those are:
|
Those are:
|
||||||
|
|
||||||
void *sbrk(int incr);
|
:::c
|
||||||
int getpid();
|
void *sbrk(int incr);
|
||||||
int fork();
|
int getpid();
|
||||||
void _exit(int rc);
|
int fork();
|
||||||
int wait(int *status);
|
void _exit(int rc);
|
||||||
int kill(int pid, int sig);
|
int wait(int *status);
|
||||||
int execve(char *name, char **argv, char **env);
|
int kill(int pid, int sig);
|
||||||
|
int execve(char *name, char **argv, char **env);
|
||||||
|
|
||||||
Let's just go through them one at a time:
|
Let's just go through them one at a time:
|
||||||
|
|
||||||
@ -27,51 +28,53 @@ The user space one makes use of the [process memory
|
|||||||
manager](/blog/2013/06/Even-More-Memory/) to return
|
manager](/blog/2013/06/Even-More-Memory/) to return
|
||||||
a chunk of new memory for the `malloc` functions.
|
a chunk of new memory for the `malloc` functions.
|
||||||
|
|
||||||
void *usr_sbrk(int incr)
|
:::c
|
||||||
{
|
void *usr_sbrk(int incr)
|
||||||
process_t *p = current->proc;
|
{
|
||||||
mem_area_t *area = find_including(p, p->mm.data_end);
|
process_t *p = current->proc;
|
||||||
if(area)
|
mem_area_t *area = find_including(p, p->mm.data_end);
|
||||||
{
|
if(area)
|
||||||
if(area->end > (p->mm.data_end + incr))
|
{
|
||||||
{
|
if(area->end > (p->mm.data_end + incr))
|
||||||
// The current memory area is large enough
|
{
|
||||||
} else {
|
// The current memory area is large enough
|
||||||
// Increase memory area
|
} else {
|
||||||
new_area(p, area->end, p->mm.data_end + incr, \
|
// Increase memory area
|
||||||
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
new_area(p, area->end, p->mm.data_end + incr, \
|
||||||
MM_TYPE_DATA);
|
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
||||||
}
|
MM_TYPE_DATA);
|
||||||
} else {
|
}
|
||||||
// Create a new memory area
|
} else {
|
||||||
new-area(p, p->mm.data_end, p->mm.data_end + incr, \
|
// Create a new memory area
|
||||||
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
new-area(p, p->mm.data_end, p->mm.data_end + incr, \
|
||||||
MM_TYPE_DATA);
|
MM_FLAG_READ | MM_FLAG_WRITE | MM_FLAG_CANSHARE, \
|
||||||
}
|
MM_TYPE_DATA);
|
||||||
p->mm.data_end = p->mm.data_end + incr;
|
}
|
||||||
return (void *)(p->mm.data_end - incr);
|
p->mm.data_end = p->mm.data_end + incr;
|
||||||
}
|
return (void *)(p->mm.data_end - incr);
|
||||||
|
}
|
||||||
|
|
||||||
The kernel space version is just a simple linear allocator
|
The kernel space version is just a simple linear allocator
|
||||||
|
|
||||||
uintptr_t kmem_top = KERNEL_HEAP_START;
|
:::c
|
||||||
uintptr_t kmem_ptr = KERNEL_HEAP_START;
|
uintptr_t kmem_top = KERNEL_HEAP_START;
|
||||||
void *sbrk(int incr)
|
uintptr_t kmem_ptr = KERNEL_HEAP_START;
|
||||||
{
|
void *sbrk(int incr)
|
||||||
if(kmem_ptr + incr > KERNEL_HEAP_END)
|
{
|
||||||
{
|
if(kmem_ptr + incr > KERNEL_HEAP_END)
|
||||||
// PANIC!
|
{
|
||||||
...
|
// PANIC!
|
||||||
}
|
...
|
||||||
while(kmem_top < kmem_ptr + incr)
|
}
|
||||||
{
|
while(kmem_top < kmem_ptr + incr)
|
||||||
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
|
{
|
||||||
PAGE_PRESENT | PAGE_WRITE));
|
vmm_page_set(kmem_top, vmm_page_val(pmm_alloc_page(), \
|
||||||
kmem_top += PAGE_SIZE;
|
PAGE_PRESENT | PAGE_WRITE));
|
||||||
}
|
kmem_top += PAGE_SIZE;
|
||||||
kmem_ptr = kmem_ptr + incr;
|
}
|
||||||
return (void *)kmem_ptr - incr;
|
kmem_ptr = kmem_ptr + incr;
|
||||||
}
|
return (void *)kmem_ptr - incr;
|
||||||
|
}
|
||||||
|
|
||||||
Hopefully it's obvious why the kernel one is called `sbrk` while the
|
Hopefully it's obvious why the kernel one is called `sbrk` while the
|
||||||
user one has a different name.
|
user one has a different name.
|
||||||
@ -79,42 +82,45 @@ user one has a different name.
|
|||||||
###getpid
|
###getpid
|
||||||
`getpid` is rather obvious:
|
`getpid` is rather obvious:
|
||||||
|
|
||||||
int getpid()
|
:::c
|
||||||
{
|
int getpid()
|
||||||
return current->proc->pid;
|
{
|
||||||
}
|
return current->proc->pid;
|
||||||
|
}
|
||||||
|
|
||||||
###fork
|
###fork
|
||||||
`fork` clones the current process and starts a new thread of execution.
|
`fork` clones the current process and starts a new thread of execution.
|
||||||
|
|
||||||
int fork()
|
:::c
|
||||||
{
|
int fork()
|
||||||
process_t *child = fork_process();
|
{
|
||||||
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
|
process_t *child = fork_process();
|
||||||
ch_thread->r.eax = 0;
|
thread_t *ch_thread = list_entry(child->threads.next, thread_t, process_threads);
|
||||||
scheduler_insert(ch_thread);
|
ch_thread->r.eax = 0;
|
||||||
return child->pid;
|
scheduler_insert(ch_thread);
|
||||||
}
|
return child->pid;
|
||||||
|
}
|
||||||
|
|
||||||
###_exit
|
###_exit
|
||||||
`_exit` stops a program and wakes up any processes that are sleeping on
|
`_exit` stops a program and wakes up any processes that are sleeping on
|
||||||
it.
|
it.
|
||||||
|
|
||||||
void _exit(int rc)
|
:::c
|
||||||
{
|
void _exit(int rc)
|
||||||
process_t *p = current->proc;
|
{
|
||||||
|
process_t *p = current->proc;
|
||||||
// Close all open files
|
|
||||||
int i;
|
// Close all open files
|
||||||
for(i = 0; i < NUM_FILEDES; i++)
|
int i;
|
||||||
{
|
for(i = 0; i < NUM_FILEDES; i++)
|
||||||
if(p->fd[i])
|
{
|
||||||
close(i);
|
if(p->fd[i])
|
||||||
}
|
close(i);
|
||||||
exit_process(current->proc, rc);
|
}
|
||||||
current->state = THREAD_STATE_FINISHED;
|
exit_process(current->proc, rc);
|
||||||
schedule();
|
current->state = THREAD_STATE_FINISHED;
|
||||||
}
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
`_exit` doesn't return, and in fact `schedule()` will never return as
|
`_exit` doesn't return, and in fact `schedule()` will never return as
|
||||||
far as this thread is concerned.
|
far as this thread is concerned.
|
||||||
@ -125,18 +131,19 @@ its parent process has executed a `wait` syscall.
|
|||||||
Actually, I didn't quite implement `wait` yet, but instead use
|
Actually, I didn't quite implement `wait` yet, but instead use
|
||||||
a `waitpid` for now, which is a bit more specific:
|
a `waitpid` for now, which is a bit more specific:
|
||||||
|
|
||||||
int waitpid(int pid)
|
:::c
|
||||||
{
|
int waitpid(int pid)
|
||||||
process_t *proc = get_process(pid);
|
{
|
||||||
while(proc->state != PROC_STATE_FINISHED)
|
process_t *proc = get_process(pid);
|
||||||
{
|
while(proc->state != PROC_STATE_FINISHED)
|
||||||
scheduler_sleep(current, &proc->waiting);
|
{
|
||||||
schedule();
|
scheduler_sleep(current, &proc->waiting);
|
||||||
}
|
schedule();
|
||||||
int ret = proc->exit_code;
|
}
|
||||||
free_process(proc);
|
int ret = proc->exit_code;
|
||||||
return ret;
|
free_process(proc);
|
||||||
}
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
This _should_ contain a check that process `pid` is a child of the
|
This _should_ contain a check that process `pid` is a child of the
|
||||||
current process too...
|
current process too...
|
||||||
@ -162,104 +169,111 @@ is:
|
|||||||
First of all, the executable is found. If it doesn't exist, we want to
|
First of all, the executable is found. If it doesn't exist, we want to
|
||||||
fail as early as possible - before we destroy everything.
|
fail as early as possible - before we destroy everything.
|
||||||
|
|
||||||
int execve(char *name, char **argv, char **env)
|
:::c
|
||||||
{
|
int execve(char *name, char **argv, char **env)
|
||||||
INODE executable = vfs_namei(name);
|
{
|
||||||
if(!executable)
|
INODE executable = vfs_namei(name);
|
||||||
{
|
if(!executable)
|
||||||
errno = ENOENT;
|
{
|
||||||
return -1;
|
errno = ENOENT;
|
||||||
}
|
return -1;
|
||||||
...
|
}
|
||||||
|
...
|
||||||
|
|
||||||
The arguments and environment are null-terminated lists of strings
|
The arguments and environment are null-terminated lists of strings
|
||||||
stored in user space, so they have to be copied into kernel space before
|
stored in user space, so they have to be copied into kernel space before
|
||||||
the user space is destroyed:
|
the user space is destroyed:
|
||||||
|
|
||||||
...
|
:::c
|
||||||
usigned int envc = 0;
|
...
|
||||||
char **temp_env = 0;
|
usigned int envc = 0;
|
||||||
if(env)
|
char **temp_env = 0;
|
||||||
{
|
if(env)
|
||||||
while(env[envc++]); // Count number of environmental variables
|
{
|
||||||
|
while(env[envc++]); // Count number of environmental variables
|
||||||
temp_env = calloc(envc, sizeof(char *));
|
|
||||||
unsigned int i = 0;
|
temp_env = calloc(envc, sizeof(char *));
|
||||||
while(env[i])
|
unsigned int i = 0;
|
||||||
{
|
while(env[i])
|
||||||
temp_env[i] = strdup(env[i]);
|
{
|
||||||
i++;
|
temp_env[i] = strdup(env[i]);
|
||||||
}
|
i++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Do the same thing for argv
|
|
||||||
...
|
// Do the same thing for argv
|
||||||
|
...
|
||||||
|
|
||||||
Next, Delete all memory from the previous executable and [load the new
|
Next, Delete all memory from the previous executable and [load the new
|
||||||
one](/blog/2013/08/Loading-Elf/):
|
one](/blog/2013/08/Loading-Elf/):
|
||||||
|
|
||||||
procmm_removeall(current->proc);
|
:::c
|
||||||
load_elf(executable);
|
procmm_removeall(current->proc);
|
||||||
current->r.eax = current->r.ebx = current->r.ecx = \
|
load_elf(executable);
|
||||||
current->r.edx = 0;
|
current->r.eax = current->r.ebx = current->r.ecx = \
|
||||||
|
current->r.edx = 0;
|
||||||
|
|
||||||
We need to put the arguments and environment back into the new
|
We need to put the arguments and environment back into the new
|
||||||
executable's user space, so a new stack area is created:
|
executable's user space, so a new stack area is created:
|
||||||
|
|
||||||
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
|
:::c
|
||||||
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
|
new_area(current->proc, USER_STACK_TOP, USER_STACK_TOP, \
|
||||||
MM_TYPE_STACK);
|
MM_FLAG_WRITE | MM_FLAG_GROWSDOWN | MM_FLAG_ADDONUSE, \
|
||||||
current->kernel_thread = (registers_t *)current;
|
MM_TYPE_STACK);
|
||||||
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
|
current->kernel_thread = (registers_t *)current;
|
||||||
|
uint32_t *pos = (uint32_t *)USER_STACK_TOP;
|
||||||
|
|
||||||
Then, copy the environment and arguments onto the stack:
|
Then, copy the environment and arguments onto the stack:
|
||||||
|
|
||||||
if(env)
|
:::c
|
||||||
{
|
if(env)
|
||||||
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
|
{
|
||||||
env = (char **)pos;
|
pos = pos - envc*sizeof(char *)/sizeof(uint32_t) - 1;
|
||||||
int i = 0;
|
env = (char **)pos;
|
||||||
while(temp_env[i])
|
int i = 0;
|
||||||
{
|
while(temp_env[i])
|
||||||
pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
|
{
|
||||||
memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
|
pos = pos - strlen(temp_env[i])/sizeof(uint32_t) - 2;
|
||||||
env[i] = (char *)pos;
|
memcpy(pos, temp_env[i], strlen(temp_env[i])+1);
|
||||||
i++;
|
env[i] = (char *)pos;
|
||||||
}
|
i++;
|
||||||
env[envc-1] = 0;
|
}
|
||||||
}
|
env[envc-1] = 0;
|
||||||
// Do the same for argc
|
}
|
||||||
...
|
// Do the same for argc
|
||||||
|
...
|
||||||
|
|
||||||
And finally, push the argument count, argument list and environment list
|
And finally, push the argument count, argument list and environment list
|
||||||
onto the stack:
|
onto the stack:
|
||||||
|
|
||||||
pos = pos - 3;
|
:::c
|
||||||
pos[0] = (uint32_t)argc - 1;
|
pos = pos - 3;
|
||||||
pos[1] = (uint32_t)argv;
|
pos[0] = (uint32_t)argc - 1;
|
||||||
pos[2] = (uint32_t)env;
|
pos[1] = (uint32_t)argv;
|
||||||
|
pos[2] = (uint32_t)env;
|
||||||
current->r.useresp = current->r.ebp = (uint32_t)pos;
|
|
||||||
current->r.ecx = (uint32_t)pos;
|
current->r.useresp = current->r.ebp = (uint32_t)pos;
|
||||||
|
current->r.ecx = (uint32_t)pos;
|
||||||
return 0;
|
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
This pushes argc, argv and env as arguments to the executabl. We can
|
This pushes argc, argv and env as arguments to the executabl. We can
|
||||||
use this to set up the `environ` variable of newlib. The crt0 in newlib
|
use this to set up the `environ` variable of newlib. The crt0 in newlib
|
||||||
pushes `ecx` to the stack and then calls `_init` which looks like this:
|
pushes `ecx` to the stack and then calls `_init` which looks like this:
|
||||||
|
|
||||||
extern char **environ;
|
:::c
|
||||||
void _init(uint32_t *args)
|
extern char **environ;
|
||||||
{
|
void _init(uint32_t *args)
|
||||||
int argc;
|
{
|
||||||
char **argv;
|
int argc;
|
||||||
if(args)
|
char **argv;
|
||||||
{
|
if(args)
|
||||||
argc = args[0];
|
{
|
||||||
argv = (char **)args[1];
|
argc = args[0];
|
||||||
environ = (char **)args[2];
|
argv = (char **)args[1];
|
||||||
} else {...}
|
environ = (char **)args[2];
|
||||||
|
} else {...}
|
||||||
exit(main(argc, argv));
|
|
||||||
}
|
exit(main(argc, argv));
|
||||||
|
}
|
||||||
|
@ -17,15 +17,17 @@ I'll just look at `signal` and `kill` and ignore things like
|
|||||||
First of all, in order to compile newlib with kernel supported signals,
|
First of all, in order to compile newlib with kernel supported signals,
|
||||||
you need the line
|
you need the line
|
||||||
|
|
||||||
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
|
:::make
|
||||||
|
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
|
||||||
|
|
||||||
in your entry in `newlib/configure.host` as described in [the osdev
|
in your entry in `newlib/configure.host` as described in [the osdev
|
||||||
wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
|
wiki](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
|
||||||
|
|
||||||
Then you need the syscalls:
|
Then you need the syscalls:
|
||||||
|
|
||||||
sig_t signal(int signum, sig_t handler);
|
:::c
|
||||||
int kill(int pid, int sig);
|
sig_t signal(int signum, sig_t handler);
|
||||||
|
int kill(int pid, int sig);
|
||||||
|
|
||||||
###Raising signals
|
###Raising signals
|
||||||
|
|
||||||
@ -38,31 +40,33 @@ as I go along later.
|
|||||||
The way I chose to implement signals was through a list for each
|
The way I chose to implement signals was through a list for each
|
||||||
process. When a signal is sent using `kill` or similar, a `signal_t`
|
process. When a signal is sent using `kill` or similar, a `signal_t`
|
||||||
|
|
||||||
typedef struct
|
:::c
|
||||||
{
|
typedef struct
|
||||||
uint32_t sig;
|
{
|
||||||
uint32_t sender;
|
uint32_t sig;
|
||||||
list_head_t queue;
|
uint32_t sender;
|
||||||
} signal_t;
|
list_head_t queue;
|
||||||
|
} signal_t;
|
||||||
|
|
||||||
is created and added to the processes list:
|
is created and added to the processes list:
|
||||||
|
|
||||||
int signal_process(int pid, int signum)
|
:::c
|
||||||
{
|
int signal_process(int pid, int signum)
|
||||||
process_t *p = get_process(pid);
|
{
|
||||||
...
|
process_t *p = get_process(pid);
|
||||||
signal_t *signal = calloc(1, sizeof(signal_t));
|
...
|
||||||
signal->sig = signum;
|
signal_t *signal = calloc(1, sizeof(signal_t));
|
||||||
signal->sender = current->proc->pid;
|
signal->sig = signum;
|
||||||
init_list(signal->queue);
|
signal->sender = current->proc->pid;
|
||||||
append_to_list(p->signal_queue, signal->queue);
|
init_list(signal->queue);
|
||||||
|
append_to_list(p->signal_queue, signal->queue);
|
||||||
|
|
||||||
if(p == current->proc)
|
if(p == current->proc)
|
||||||
{
|
{
|
||||||
handle_signals(current);
|
handle_signals(current);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
If the currently running thread is in the process being signalled, we
|
If the currently running thread is in the process being signalled, we
|
||||||
handle the signals immediately. Otherwise it can wait for a bit.
|
handle the signals immediately. Otherwise it can wait for a bit.
|
||||||
@ -75,13 +79,14 @@ The `signal` syscall lets the process select how to handle a certain
|
|||||||
signal. Each process also contains a table of `sig_t` and the `signal`
|
signal. Each process also contains a table of `sig_t` and the `signal`
|
||||||
syscall calls the following function:
|
syscall calls the following function:
|
||||||
|
|
||||||
sig_t switch_handler(int signum, sig_t handler)
|
:::c
|
||||||
{
|
sig_t switch_handler(int signum, sig_t handler)
|
||||||
...
|
{
|
||||||
sig_t old = current->proc->signal_handler[signum];
|
...
|
||||||
current->proc->signal_handler[signum] = handler;
|
sig_t old = current->proc->signal_handler[signum];
|
||||||
return old;
|
current->proc->signal_handler[signum] = handler;
|
||||||
}
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
The cut out part of this function contains code to make sure the handler
|
The cut out part of this function contains code to make sure the handler
|
||||||
of signal 9 (`SIGKILL`) is never changed from `SIG_DFL`.
|
of signal 9 (`SIGKILL`) is never changed from `SIG_DFL`.
|
||||||
@ -94,24 +99,25 @@ as the process continues running should be soon enough.
|
|||||||
|
|
||||||
I chose to hook into the my interrupt handler:
|
I chose to hook into the my interrupt handler:
|
||||||
|
|
||||||
registers_t *idt_handler(registers_t *r)
|
:::c
|
||||||
{
|
registers_t *idt_handler(registers_t *r)
|
||||||
...
|
{
|
||||||
if(int_handlers[r->int_no)
|
...
|
||||||
{
|
if(int_handlers[r->int_no)
|
||||||
...
|
{
|
||||||
registers_t *ret = int_handlers[r->int_no](r);
|
...
|
||||||
|
registers_t *ret = int_handlers[r->int_no](r);
|
||||||
if((ret->cs & 0x3) == 0x3)
|
|
||||||
{
|
if((ret->cs & 0x3) == 0x3)
|
||||||
ret = (registers_t *)handle_signals((thread_t *)ret);
|
{
|
||||||
...
|
ret = (registers_t *)handle_signals((thread_t *)ret);
|
||||||
}
|
...
|
||||||
...
|
}
|
||||||
return ret
|
...
|
||||||
}
|
return ret
|
||||||
...
|
}
|
||||||
}
|
...
|
||||||
|
}
|
||||||
|
|
||||||
So, what does `handle_signals` actually do?
|
So, what does `handle_signals` actually do?
|
||||||
|
|
||||||
@ -125,43 +131,45 @@ If the handler is set to `SIG_DFL` the function looks up the correct
|
|||||||
default signal handler in a table and calls it. It may be one of the
|
default signal handler in a table and calls it. It may be one of the
|
||||||
following:
|
following:
|
||||||
|
|
||||||
void sighandler_ignore(int num)
|
:::c
|
||||||
{
|
void sighandler_ignore(int num)
|
||||||
(void)num;
|
{
|
||||||
}
|
(void)num;
|
||||||
void sighandler_terminate(int num)
|
}
|
||||||
{
|
void sighandler_terminate(int num)
|
||||||
fprintf(stderr,, "Process %x terminated by signal %x\n", \
|
{
|
||||||
current->proc->pid, num);
|
fprintf(stderr,, "Process %x terminated by signal %x\n", \
|
||||||
_exit(num);
|
current->proc->pid, num);
|
||||||
}
|
_exit(num);
|
||||||
void sighandler_coredump(int num)
|
}
|
||||||
{ /* Same as above */ }
|
void sighandler_coredump(int num)
|
||||||
void sighandler_stop(int num)
|
{ /* Same as above */ }
|
||||||
{ /* Not implemented yet */ }
|
void sighandler_stop(int num)
|
||||||
void sighandler_continue(int num)
|
{ /* Not implemented yet */ }
|
||||||
{ /* Not implemented yet */ }
|
void sighandler_continue(int num)
|
||||||
|
{ /* Not implemented yet */ }
|
||||||
|
|
||||||
If the handler is a function a new thread is created to handle it:
|
If the handler is a function a new thread is created to handle it:
|
||||||
|
|
||||||
...
|
:::c
|
||||||
sig_t handler = th->proc->signal_handler[signal->sig];
|
...
|
||||||
thread_t *h = new_thread((void (*)(void))handler, 1);
|
sig_t handler = th->proc->signal_handler[signal->sig];
|
||||||
|
thread_t *h = new_thread((void (*)(void))handler, 1);
|
||||||
append_to_list(th->proc->threads, h->process_threads);
|
|
||||||
h->proc = th->proc;
|
append_to_list(th->proc->threads, h->process_threads);
|
||||||
uint32_t *stack = ((uint32_t *)th->r.useresp;
|
h->proc = th->proc;
|
||||||
*--stack = signal->sig;
|
uint32_t *stack = ((uint32_t *)th->r.useresp;
|
||||||
*--stack = SIGNAL_RETURN_ADDRESS;
|
*--stack = signal->sig;
|
||||||
h->r.useresp = h->r.ebp = (uint32_t)stack;
|
*--stack = SIGNAL_RETURN_ADDRESS;
|
||||||
remove_from_list(signal->queue);
|
h->r.useresp = h->r.ebp = (uint32_t)stack;
|
||||||
free(signal);
|
remove_from_list(signal->queue);
|
||||||
|
free(signal);
|
||||||
scheduler_remove(h);
|
|
||||||
scheduler_sleep(th, &h->waiting);
|
scheduler_remove(h);
|
||||||
scheduler_cheat(h);
|
scheduler_sleep(th, &h->waiting);
|
||||||
schedule();
|
scheduler_cheat(h);
|
||||||
}
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
This creates a new thread and pushes the signal number (as an argument)
|
This creates a new thread and pushes the signal number (as an argument)
|
||||||
and a return address to the threads stack. It then places the new thread
|
and a return address to the threads stack. It then places the new thread
|
||||||
|
@ -25,7 +25,8 @@ I've described how I use tmux with qemu, gdb and telnet
|
|||||||
pane which displays the debug information, but one thing at a time.
|
pane which displays the debug information, but one thing at a time.
|
||||||
Here's what I did in a bash script:
|
Here's what I did in a bash script:
|
||||||
|
|
||||||
tmux split-window -h 'qemu-system-i386 -kernel build/kernel/kernel -initrd "build/tarfs.tar" -curses -monitor telnet:localhost:4444,server -s -S -serial file:serial.out'
|
:::bash
|
||||||
|
tmux split-window -h 'qemu-system-i386 -kernel build/kernel/kernel -initrd "build/tarfs.tar" -curses -monitor telnet:localhost:4444,server -s -S -serial file:serial.out'
|
||||||
|
|
||||||
This opens a new tmux pane with qemu running my kernel and loading
|
This opens a new tmux pane with qemu running my kernel and loading
|
||||||
`tarfs.tar` as a multiboot module. The `-monitor` flag starts a telnet
|
`tarfs.tar` as a multiboot module. The `-monitor` flag starts a telnet
|
||||||
@ -36,34 +37,37 @@ from serial port 1 to a file.
|
|||||||
|
|
||||||
The next step is:
|
The next step is:
|
||||||
|
|
||||||
tmux split-window -v 'tail -f serial.out | util/colorize.sh'
|
:::bash
|
||||||
|
tmux split-window -v 'tail -f serial.out | util/colorize.sh'
|
||||||
|
|
||||||
which opens up another tmux pane that displays the serial output using
|
which opens up another tmux pane that displays the serial output using
|
||||||
the auto-updating feature of `tail`. The script `colorize.sh` colorizes
|
the auto-updating feature of `tail`. The script `colorize.sh` colorizes
|
||||||
certain words of the output:
|
certain words of the output:
|
||||||
|
|
||||||
#!/usr/bin/env bash
|
:::bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
C_NO=`echo -e "\\033[0m"`
|
|
||||||
C_RED=`echo -e "\\033[31m"`
|
C_NO=`echo -e "\\033[0m"`
|
||||||
C_GREEN=`echo -e "\\033[32m"`
|
C_RED=`echo -e "\\033[31m"`
|
||||||
C_YELLOW=`echo -e "\\033[33m"`
|
C_GREEN=`echo -e "\\033[32m"`
|
||||||
C_BLUE=`echo -e "\\033[36m"`
|
C_YELLOW=`echo -e "\\033[33m"`
|
||||||
|
C_BLUE=`echo -e "\\033[36m"`
|
||||||
while read line; do
|
|
||||||
echo $line | sed \
|
while read line; do
|
||||||
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
|
echo $line | sed \
|
||||||
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
|
-e "s/\(\[info\]\)/${C_BLUE}\1$[C_NO}/" \
|
||||||
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
|
-e "s/\(\[status\]\)/${C_GREEN}\1$[C_NO}/" \
|
||||||
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
|
-e "s/\(\[warning\]\)/${C_YELLOW}\1$[C_NO}/" \
|
||||||
done
|
-e "s/\(\[error\]\)/${C_RED}\1$[C_NO}/"
|
||||||
|
done
|
||||||
|
|
||||||
Next is
|
Next is
|
||||||
|
|
||||||
tmux select-pane -L
|
:::bash
|
||||||
tmux split-window -v 'i586-elf-gdb'
|
tmux select-pane -L
|
||||||
tmux select-pane -U
|
tmux split-window -v 'i586-elf-gdb'
|
||||||
telnet localhost 4444
|
tmux select-pane -U
|
||||||
|
telnet localhost 4444
|
||||||
|
|
||||||
Which opens a new pane for the `gdb` debugger and then moves back to the
|
Which opens a new pane for the `gdb` debugger and then moves back to the
|
||||||
first pane to open the telnet terminal.
|
first pane to open the telnet terminal.
|
||||||
@ -76,38 +80,41 @@ Printing](/media/img/debug_print.png)](/media/img/debug_print_full.png)
|
|||||||
Notice the yellow lines in that screenshot?
|
Notice the yellow lines in that screenshot?
|
||||||
The ones that say
|
The ones that say
|
||||||
|
|
||||||
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
|
Kernel git data: [5e6074a (dirty)] Fri Mar 7 13:45:31 2014 +0100
|
||||||
(HEAD, harddrive): Ext2 unlink function. Untested.
|
(HEAD, harddrive): Ext2 unlink function. Untested.
|
||||||
Kernel compilation: Mar 7 2014 14:06:27
|
Kernel compilation: Mar 7 2014 14:06:27
|
||||||
|
|
||||||
The data for this is stored in a special file called `version.c`
|
The data for this is stored in a special file called `version.c`
|
||||||
|
|
||||||
#include <version.h>
|
:::c
|
||||||
|
#include <version.h>
|
||||||
char * __kernel_git_hash = GITHASH;
|
|
||||||
char * __kernel_git_date = GITDATE;
|
char * __kernel_git_hash = GITHASH;
|
||||||
int __kernel_git_dirty = GITDIRTY;
|
char * __kernel_git_date = GITDATE;
|
||||||
char * __kernel_git_message = GITMESSAGE;
|
int __kernel_git_dirty = GITDIRTY;
|
||||||
char * __kernel_git_branch = GITBRANCH;
|
char * __kernel_git_message = GITMESSAGE;
|
||||||
|
char * __kernel_git_branch = GITBRANCH;
|
||||||
char * __kernel_build_date = __DATE__;
|
|
||||||
char * __kernel_build_time = __TIME__;
|
char * __kernel_build_date = __DATE__;
|
||||||
|
char * __kernel_build_time = __TIME__;
|
||||||
|
|
||||||
which has a special line in the kernel makefile:
|
which has a special line in the kernel makefile:
|
||||||
|
|
||||||
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
|
:::make
|
||||||
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
|
version.o: CFLAGS += -DGITHASH='$(shell git log -1 --pretty="tformat:%h")' \
|
||||||
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
|
-DGITDATE='$(shell git log -1 --pretty="tformat:%cd")' \
|
||||||
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \
|
-DGITDIRTY='$(shell [[ -n `git status -s 2> /dev/null` ]] && echo 1 || echo 0)' \
|
||||||
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")'
|
-DGITMESSATE='$(shell git log -1 --pretty="tformat:%s")' \
|
||||||
|
-DGITBRANCH='$(shell git log -1 --pretty="tformat:%d")'
|
||||||
|
|
||||||
A few more lines makes sure `version.c` is always recompiled if any
|
A few more lines makes sure `version.c` is always recompiled if any
|
||||||
other part of the kernel is:
|
other part of the kernel is:
|
||||||
|
|
||||||
kernel: $(kernel_objects)
|
:::make
|
||||||
rm -f version.o
|
kernel: $(kernel_objects)
|
||||||
$(MAKE) version.o
|
rm -f version.o
|
||||||
$(LINK)
|
$(MAKE) version.o
|
||||||
|
$(LINK)
|
||||||
|
|
||||||
Obviously, this is a bit simplified. But not much. I might make a post
|
Obviously, this is a bit simplified. But not much. I might make a post
|
||||||
about my makefiles some day...
|
about my makefiles some day...
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "DITo - Framework"
|
title: "DITo - Framework"
|
||||||
subtitle: "the Disk Image TOols"
|
subtitle: "the Disk Image TOols"
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
In my osdeving, I was starting to reach the point where a disk driver
|
In my osdeving, I was starting to reach the point where a disk driver
|
||||||
seemed like the obvious next step. This was pretty much entirely unknown
|
seemed like the obvious next step. This was pretty much entirely unknown
|
||||||
@ -42,33 +42,36 @@ some parts from scratch. Let's go!
|
|||||||
The basic operations of DITo are reading from or writing to image files
|
The basic operations of DITo are reading from or writing to image files
|
||||||
or disk drives. Each drive type has a driver
|
or disk drives. Each drive type has a driver
|
||||||
|
|
||||||
typedef struct drive_driver
|
:::c
|
||||||
{
|
typedef struct drive_driver
|
||||||
int (*open)(struct drive_t *d, int flags);
|
{
|
||||||
int (*close)(struct drive_t *d, int flags);
|
int (*open)(struct drive_t *d, int flags);
|
||||||
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
int (*close)(struct drive_t *d, int flags);
|
||||||
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
int (*read)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
||||||
} drive_driver_t;
|
int (*write)(struct drive_t *d, void *buffer, size_t length, off_t offset);
|
||||||
|
} drive_driver_t;
|
||||||
|
|
||||||
The drive type contains a pointer to the driver and a pointer to some
|
The drive type contains a pointer to the driver and a pointer to some
|
||||||
arbitrary data used by the driver.
|
arbitrary data used by the driver.
|
||||||
|
|
||||||
typedef struct drive_t
|
:::c
|
||||||
{
|
typedef struct drive_t
|
||||||
struct drive_driver *d;
|
{
|
||||||
void *data;
|
struct drive_driver *d;
|
||||||
} drive_t;
|
void *data;
|
||||||
|
} drive_t;
|
||||||
|
|
||||||
Then there are some wrapper functions for performing the required
|
Then there are some wrapper functions for performing the required
|
||||||
operations:
|
operations:
|
||||||
|
|
||||||
int drive_open(struct drive_t *d, int flags)
|
:::c
|
||||||
{
|
int drive_open(struct drive_t *d, int flags)
|
||||||
if(d->d->open)
|
{
|
||||||
return d->d->open(d, flags);
|
if(d->d->open)
|
||||||
else
|
return d->d->open(d, flags);
|
||||||
return 0;
|
else
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
and simmilar for `drive_close`, `drive_read` and `drive_write`.
|
and simmilar for `drive_close`, `drive_read` and `drive_write`.
|
||||||
|
|
||||||
@ -78,30 +81,32 @@ The next important part of DITo is the filesystem handling. After
|
|||||||
thinking about it, the important primitive functions for all file
|
thinking about it, the important primitive functions for all file
|
||||||
operations I could think about are all in a filesystem driver struct:
|
operations I could think about are all in a filesystem driver struct:
|
||||||
|
|
||||||
struct fs_driver
|
:::c
|
||||||
{
|
struct fs_driver
|
||||||
INODE (*open)(struct fs_t *fs, const char *path, int flags);
|
{
|
||||||
int (*close)(struct fs_t *fs, INODE ino);
|
INODE (*open)(struct fs_t *fs, const char *path, int flags);
|
||||||
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
int (*close)(struct fs_t *fs, INODE ino);
|
||||||
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
int (*read)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
||||||
int (*truncate)(struct fs_t *fs, INODE ino, off_t length);
|
int (*write)(struct fs_t *fs, INODE ino, void *buffer, size_t length, off_t offset);
|
||||||
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st);
|
int (*truncate)(struct fs_t *fs, INODE ino, off_t length);
|
||||||
|
int (*stat)(struct fs_t *fs, INODE ino, struct stat *st);
|
||||||
int (*touch)(struct fs_t *fs, const char *path, struct stat *st);
|
|
||||||
int (*link)(struct fs_t *fs, const char *path1, const char *path2);
|
int (*touch)(struct fs_t *fs, const char *path, struct stat *st);
|
||||||
int (*unlink)(struct fs_t *fs, const char *path);
|
int (*link)(struct fs_t *fs, const char *path1, const char *path2);
|
||||||
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num);
|
int (*unlink)(struct fs_t *fs, const char *path);
|
||||||
};
|
dirent_t *(*readdir)(struct fs_t *fs, INODE dir, unsigned int num);
|
||||||
|
};
|
||||||
|
|
||||||
The `fs_t` type contains a pointer to the driver, a pointer to the drive
|
The `fs_t` type contains a pointer to the driver, a pointer to the drive
|
||||||
and a general data pointer.
|
and a general data pointer.
|
||||||
|
|
||||||
typedef struct fs_t
|
:::c
|
||||||
{
|
typedef struct fs_t
|
||||||
struct fs_driver *driver;
|
{
|
||||||
drive_t *d;
|
struct fs_driver *driver;
|
||||||
void *data;
|
drive_t *d;
|
||||||
} fs_t;
|
void *data;
|
||||||
|
} fs_t;
|
||||||
|
|
||||||
The wrapper functions `fs_open`, `fs_close` and so on work the same way
|
The wrapper functions `fs_open`, `fs_close` and so on work the same way
|
||||||
as the `drive_*` functions.
|
as the `drive_*` functions.
|
||||||
@ -109,14 +114,15 @@ as the `drive_*` functions.
|
|||||||
The `INODE` type is a pointer to a struct containing a pointer to the
|
The `INODE` type is a pointer to a struct containing a pointer to the
|
||||||
filesystem, a unique inode number and a pointer to arbitrary data.
|
filesystem, a unique inode number and a pointer to arbitrary data.
|
||||||
|
|
||||||
struct ino_st
|
:::c
|
||||||
{
|
struct ino_st
|
||||||
fs_t *fs;
|
{
|
||||||
unsigned int ino;
|
fs_t *fs;
|
||||||
void *data;
|
unsigned int ino;
|
||||||
};
|
void *data;
|
||||||
|
};
|
||||||
typedef struct ino_st * INODE;
|
|
||||||
|
typedef struct ino_st * INODE;
|
||||||
|
|
||||||
And that's the basic framework. As you probably notice, the same `fs_t`
|
And that's the basic framework. As you probably notice, the same `fs_t`
|
||||||
pointer is passed to most functions twice. Once as `fs` and once as
|
pointer is passed to most functions twice. Once as `fs` and once as
|
||||||
@ -129,41 +135,43 @@ if I have one image of an FAT floppy disk with a file I want copied to
|
|||||||
the ext2 formated second partition of a hard drive image, I could do
|
the ext2 formated second partition of a hard drive image, I could do
|
||||||
someting like this:
|
someting like this:
|
||||||
|
|
||||||
drive_t *fat_disk = image_drive("floppy.img");
|
:::c
|
||||||
drive_open(fat_disk, READ_FLAG);
|
drive_t *fat_disk = image_drive("floppy.img");
|
||||||
drive_t *ext2_disk = image_drive("harddrive.img");
|
drive_open(fat_disk, READ_FLAG);
|
||||||
drive_open(ext2_disk, READ_WRITE_FLAG);
|
drive_t *ext2_disk = image_drive("harddrive.img");
|
||||||
drive_t *ext2_partition = mbr_drive(ext2_disk, 2);
|
drive_open(ext2_disk, READ_WRITE_FLAG);
|
||||||
drive_open(ext2_partition, READ_WRITE_FLAG);
|
drive_t *ext2_partition = mbr_drive(ext2_disk, 2);
|
||||||
|
drive_open(ext2_partition, READ_WRITE_FLAG);
|
||||||
fs_t *fat = fat_fs(fat_disk);
|
|
||||||
fs_t *ext2 = ext2_fs(ext2_partition);
|
fs_t *fat = fat_fs(fat_disk);
|
||||||
|
fs_t *ext2 = ext2_fs(ext2_partition);
|
||||||
INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
|
|
||||||
struct st *st = malloc(sizeof(struct st));
|
INODE source = fs_open(fat, "/path/to/file", READ_FLAG);
|
||||||
fs_struct(fat, source, st);
|
struct st *st = malloc(sizeof(struct st));
|
||||||
|
fs_struct(fat, source, st);
|
||||||
fs_touch(ext2, "/new/path", st);
|
|
||||||
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
|
fs_touch(ext2, "/new/path", st);
|
||||||
|
INODE destination = fs_open(ext2, "/new/path", WRITE_FLAG);
|
||||||
void *buffer = malloc(BUFER_SIZE);
|
|
||||||
off_t offset = 0;
|
void *buffer = malloc(BUFER_SIZE);
|
||||||
off_t add = 0;
|
off_t offset = 0;
|
||||||
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset))
|
off_t add = 0;
|
||||||
{
|
while(add = fs_read(fat, source, buffer, BUFFER_SIZE, offset))
|
||||||
fs_write(ext2, destination, buffer, BUFFER_SIZE, offset);
|
{
|
||||||
offset += add;
|
fs_write(ext2, destination, buffer, BUFFER_SIZE, offset);
|
||||||
}
|
offset += add;
|
||||||
|
}
|
||||||
fs_close(destination);
|
|
||||||
fs_close(source);
|
fs_close(destination);
|
||||||
|
fs_close(source);
|
||||||
drive_close(ext2_partition);
|
|
||||||
drive_close(fat_disk);
|
drive_close(ext2_partition);
|
||||||
|
drive_close(fat_disk);
|
||||||
|
|
||||||
Which of couse will eventually become its own tool so that the actual
|
Which of couse will eventually become its own tool so that the actual
|
||||||
work the end user has to do becomes:
|
work the end user has to do becomes:
|
||||||
|
|
||||||
$ dito-cp floppy.img:/path/to/file harddrive.img:2:/new/path
|
:::bash
|
||||||
|
$ dito-cp floppy.img:/path/to/file harddrive.img:2:/new/path
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "DITo - Drives"
|
title: "DITo - Drives"
|
||||||
subtitle: "Exploring the MBR"
|
subtitle: "Exploring the MBR"
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
Let's write a few drive drivers.
|
Let's write a few drive drivers.
|
||||||
|
|
||||||
@ -15,61 +15,64 @@ First of all, let's think of what data we'd need to store. The filename
|
|||||||
might be nice to have, and probably a pointer to the opened file. Would
|
might be nice to have, and probably a pointer to the opened file. Would
|
||||||
hate to lose that...
|
hate to lose that...
|
||||||
|
|
||||||
struct im_data
|
:::c
|
||||||
{
|
struct im_data
|
||||||
char *filename;
|
{
|
||||||
FILE *file;
|
char *filename;
|
||||||
};
|
FILE *file;
|
||||||
|
};
|
||||||
|
|
||||||
The functions required for the driver would then make use of the
|
The functions required for the driver would then make use of the
|
||||||
c standard library:
|
c standard library:
|
||||||
|
|
||||||
int im_open(drive_t *d, int flags)
|
:::c
|
||||||
{
|
int im_open(drive_t *d, int flags)
|
||||||
struct im_data *data = d->data;
|
{
|
||||||
data->file = fopen(data->filename, flags);
|
struct im_data *data = d->data;
|
||||||
return 1;
|
data->file = fopen(data->filename, flags);
|
||||||
}
|
return 1;
|
||||||
int im_close(drive_t *d, int flags)
|
}
|
||||||
{
|
int im_close(drive_t *d, int flags)
|
||||||
struct im_data *data = d->data;
|
{
|
||||||
fclose(data->file);
|
struct im_data *data = d->data;
|
||||||
return 1;
|
fclose(data->file);
|
||||||
}
|
return 1;
|
||||||
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
|
}
|
||||||
{
|
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
|
||||||
struct im_data *data = d->data;
|
{
|
||||||
fseek(data->file, offset, SEEK_SET);
|
struct im_data *data = d->data;
|
||||||
return fread(buffer, length, 1, data->file);
|
fseek(data->file, offset, SEEK_SET);
|
||||||
}
|
return fread(buffer, length, 1, data->file);
|
||||||
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
|
}
|
||||||
{
|
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
|
||||||
struct im_data *data = d->data;
|
{
|
||||||
fseek(data->file, offset, SEEK_SET);
|
struct im_data *data = d->data;
|
||||||
return fwrite(buffer, length, 1, data->file);
|
fseek(data->file, offset, SEEK_SET);
|
||||||
}
|
return fwrite(buffer, length, 1, data->file);
|
||||||
|
}
|
||||||
|
|
||||||
The function for setting up the drive is equally simple:
|
The function for setting up the drive is equally simple:
|
||||||
|
|
||||||
drive_driver_t im_driver =
|
:::c
|
||||||
{
|
drive_driver_t im_driver =
|
||||||
im_open,
|
{
|
||||||
im_close,
|
im_open,
|
||||||
im_read,
|
im_close,
|
||||||
im_write,
|
im_read,
|
||||||
};
|
im_write,
|
||||||
|
};
|
||||||
drive_t *image_drive(const char *filename)
|
|
||||||
{
|
drive_t *image_drive(const char *filename)
|
||||||
drive_t *d = calloc(1, sizeof(drive_t));
|
{
|
||||||
struct im_data *data = calloc(1, sizeof(struct im_data));
|
drive_t *d = calloc(1, sizeof(drive_t));
|
||||||
|
struct im_data *data = calloc(1, sizeof(struct im_data));
|
||||||
d->driver = &im_driver;
|
|
||||||
d->data = data;
|
d->driver = &im_driver;
|
||||||
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
|
d->data = data;
|
||||||
|
data->filename = strndup(filename, FILENAME_MAX_LENGTH);
|
||||||
return d;
|
|
||||||
}
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
As always, I've stripped away all sanity checking and error handling.
|
As always, I've stripped away all sanity checking and error handling.
|
||||||
|
|
||||||
@ -85,15 +88,16 @@ The MBR starts with 436 bytes of space for a bootloader, then there's a
|
|||||||
|
|
||||||
The entries of the table has the following structure:
|
The entries of the table has the following structure:
|
||||||
|
|
||||||
struct MBR
|
:::c
|
||||||
{
|
struct MBR
|
||||||
uint8_t boot_indicator;
|
{
|
||||||
uint8_t start_chs[3];
|
uint8_t boot_indicator;
|
||||||
uint8_t system_id;
|
uint8_t start_chs[3];
|
||||||
uint8_t end_chs[3];
|
uint8_t system_id;
|
||||||
uint32_t start_lba;
|
uint8_t end_chs[3];
|
||||||
uint32_t num_sectors;
|
uint32_t start_lba;
|
||||||
}__attribute__((packed));
|
uint32_t num_sectors;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
_CHS_ and _LBA_ are different ways of addressing sectors of the disk.
|
_CHS_ and _LBA_ are different ways of addressing sectors of the disk.
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
layout: post
|
layout: post
|
||||||
title: "DITo - Ext2"
|
title: "DITo - Ext2"
|
||||||
subtitle: "A beginning..."
|
subtitle: "A beginning..."
|
||||||
tags: [osdev]
|
tags: [osdev, filesystems]
|
||||||
|
|
||||||
It's finally time to start looking at an actual filesystem.
|
It's finally time to start looking at an actual filesystem.
|
||||||
|
|
||||||
I chose to begin with ext2.
|
I chose to begin with ext2.
|
||||||
|
@ -3,7 +3,7 @@ permalink: /about/
|
|||||||
|
|
||||||
Thomas Lovén
|
Thomas Lovén
|
||||||
------------
|
------------
|
||||||
<img src="/media/img/thomas.png" width="300" class="right">
|
{: width="300" .right}
|
||||||
|
|
||||||
Born in Karlskrona in the south of Sweden. Lived there for 18 years
|
Born in Karlskrona in the south of Sweden. Lived there for 18 years
|
||||||
before doing army service in Eksjö and then moving to Gothenburg for
|
before doing army service in Eksjö and then moving to Gothenburg for
|
||||||
|
@ -10,37 +10,36 @@ Translation and grades can be provided on request.
|
|||||||
|
|
||||||
I earned my bachelors degree in 2012.
|
I earned my bachelors degree in 2012.
|
||||||
|
|
||||||
TDA305 Datorintroduktion 1,5hp
|
TDA305 Datorintroduktion 1,5hp
|
||||||
ESS115 Elektriska nät och system 7,5hp
|
ESS115 Elektriska nät och system 7,5hp
|
||||||
EEF031 Elektromagnetisk fältteori 7,5hp
|
EEF031 Elektromagnetisk fältteori 7,5hp
|
||||||
TIF080 Experimentell fysik 1 - mätteknik 9hp
|
TIF080 Experimentell fysik 1 - mätteknik 9hp
|
||||||
TIF090 Experimentell fysik 2 - bas 4,5hp
|
TIF090 Experimentell fysik 2 - bas 4,5hp
|
||||||
FFY011 Fasta tillståndets fysik 7,5hp
|
FFY011 Fasta tillståndets fysik 7,5hp
|
||||||
MVE035 Flervariabelanalys 6hp
|
MVE035 Flervariabelanalys 6hp
|
||||||
MVE030 Fourieranalys 6hp
|
MVE030 Fourieranalys 6hp
|
||||||
TIF010 Fysiken omkring oss 7,5hp
|
TIF010 Fysiken omkring oss 7,5hp
|
||||||
TMA970 Inledande matematisk analys 6hp
|
TMA970 Inledande matematisk analys 6hp
|
||||||
FKA150 Inledande teknisk kommunikation 1,5hp
|
FKA150 Inledande teknisk kommunikation 1,5hp
|
||||||
TIFX02 Kandidatarbete vid Teknisk fysik 15hp
|
TIFX02 Kandidatarbete vid Teknisk fysik 15hp
|
||||||
MVE025 Komplex matematisk analys 6hp
|
MVE025 Komplex matematisk analys 6hp
|
||||||
FUF040 Kvantfysik 6hp
|
FUF040 Kvantfysik 6hp
|
||||||
TMA660 Linjär algebra och geometri 4,5hp
|
TMA660 Linjär algebra och geometri 4,5hp
|
||||||
TMA671 Linjär algebra och numerisk analys 7,5hp
|
TMA671 Linjär algebra och numerisk analys 7,5hp
|
||||||
TMA976 Matematisk analys, fortsättning 6hp
|
TMA976 Matematisk analys, fortsättning 6hp
|
||||||
TMA321 Matematisk statistik 4,5hp
|
TMA321 Matematisk statistik 4,5hp
|
||||||
FFM515 Mekanik 1 7,5hp
|
FFM515 Mekanik 1 7,5hp
|
||||||
FFM520 Mekanik 2 6hp
|
FFM520 Mekanik 2 6hp
|
||||||
TIF075 Miljöfysik 4,5hp
|
TIF075 Miljöfysik 4,5hp
|
||||||
FFY091 Optik 6hp
|
FFY091 Optik 6hp
|
||||||
TIN211 Programmeringsteknik 6hp
|
TIN211 Programmeringsteknik 6hp
|
||||||
ERE091 Reglerteknik 4,5hp
|
ERE091 Reglerteknik 4,5hp
|
||||||
FUF045 Speciell relativitetsteori 4,5hp
|
FUF045 Speciell relativitetsteori 4,5hp
|
||||||
TME055 Strömningsmekanik 4,5hp
|
TME055 Strömningsmekanik 4,5hp
|
||||||
FUF050 Subatomär fysik 6hp
|
FUF050 Subatomär fysik 6hp
|
||||||
FTF140 Termodynamik och statistisk fysik 7,5hp
|
FTF140 Termodynamik och statistisk fysik 7,5hp
|
||||||
TIF100 Tillämpad kvantfysik 4,5hp
|
TIF100 Tillämpad kvantfysik 4,5hp
|
||||||
FFM232 Vektorfält och klassisk fysik 4,5hp
|
FFM232 Vektorfält och klassisk fysik 4,5hp
|
||||||
{: .prettyprint .lang-betyg}
|
|
||||||
|
|
||||||
###Bachelors thesis
|
###Bachelors thesis
|
||||||
For our bachelors thesis me, two of my fellow students at Engineering
|
For our bachelors thesis me, two of my fellow students at Engineering
|
||||||
@ -57,20 +56,19 @@ Grades can be provided on request.
|
|||||||
|
|
||||||
As of January 2013 I have just begun work on my Masters thesis.
|
As of January 2013 I have just begun work on my Masters thesis.
|
||||||
|
|
||||||
TIN092 Algorithms 7,5hp
|
TIN092 Algorithms 7,5hp
|
||||||
FFR135 Artificial neural networks 7,5hp
|
FFR135 Artificial neural networks 7,5hp
|
||||||
FFR125 Autonomous agents 7,5hp
|
FFR125 Autonomous agents 7,5hp
|
||||||
FFR141 Complex systems seminar 7,5hp
|
FFR141 Complex systems seminar 7,5hp
|
||||||
FFR110 Computational biology 1 7,5hp
|
FFR110 Computational biology 1 7,5hp
|
||||||
TDA351 Cryptography 7,5hp
|
TDA351 Cryptography 7,5hp
|
||||||
TIF115 Dynamical systems 7,5hp
|
TIF115 Dynamical systems 7,5hp
|
||||||
TIF160 Humanoid robotics 7,5hp
|
TIF160 Humanoid robotics 7,5hp
|
||||||
RRY025 Image processing 7,5hp
|
RRY025 Image processing 7,5hp
|
||||||
MVE370 Matematik och samhälle 7,5hp
|
MVE370 Matematik och samhälle 7,5hp
|
||||||
FFR120 Simulation of complex systems 7,5hp
|
FFR120 Simulation of complex systems 7,5hp
|
||||||
FFR105 Stochastic optimization algorithms 7,5hp
|
FFR105 Stochastic optimization algorithms 7,5hp
|
||||||
MVE080 Vetenskaplig visualisering 7,5hp
|
MVE080 Vetenskaplig visualisering 7,5hp
|
||||||
{: .prettyprint .lang-betyg}
|
|
||||||
|
|
||||||
__Matematik och samhälle__ translates to __Mathematics and society__.
|
__Matematik och samhälle__ translates to __Mathematics and society__.
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ DEBUG = True
|
|||||||
FLATPAGES_AUTO_RELOAD = DEBUG
|
FLATPAGES_AUTO_RELOAD = DEBUG
|
||||||
FLATPAGES_EXTENSION = '.md'
|
FLATPAGES_EXTENSION = '.md'
|
||||||
FLATPAGES_ROOT = 'pages'
|
FLATPAGES_ROOT = 'pages'
|
||||||
|
FLATPAGES_MARKDOWN_EXTENSIONS = ['codehilite', 'extra']
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(__name__)
|
app.config.from_object(__name__)
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<!-- MathJax.Hub.Config({ -->
|
<!-- MathJax.Hub.Config({ -->
|
||||||
<!-- tex2jax: { -->
|
<!-- tex2jax: { -->
|
||||||
<!-- skipTags: ["script","noscript","style","textarea","code"], -->
|
<!-- skipTags: ["script","noscript","style","textarea","code"], -->
|
||||||
<!-- ignoreClass: ".*", -->
|
<!-- ignoreClass: ".*", -->
|
||||||
<!-- processClass: "latex" -->
|
<!-- processClass: "latex" -->
|
||||||
<!-- } -->
|
<!-- } -->
|
||||||
<!-- }); -->
|
<!-- }); -->
|
||||||
@ -67,12 +67,12 @@
|
|||||||
</div><!--wrap-->
|
</div><!--wrap-->
|
||||||
|
|
||||||
<div id="bottom">
|
<div id="bottom">
|
||||||
© 2012
|
© 2012
|
||||||
<a href="mailto:thomasloven@gmail.com">Thomas Lovén</a>
|
<a href="mailto:thomasloven@gmail.com">Thomas Lovén</a>
|
||||||
-
|
-
|
||||||
<a href="http://twitter.com/thomasloven">@thomasloven</a>
|
<a href="http://twitter.com/thomasloven">@thomasloven</a>
|
||||||
-
|
-
|
||||||
<a href="http://github.com/thomasloven">GitHub</a>
|
<a href="http://github.com/thomasloven">GitHub</a>
|
||||||
</div><!--bottom-->
|
</div><!--bottom-->
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,14 +2,20 @@
|
|||||||
|
|
||||||
{% block page %}
|
{% block page %}
|
||||||
<div id="categories">
|
<div id="categories">
|
||||||
<a href="{{url_for("blog_default")}}">All</a>
|
{% if title %}
|
||||||
|
<a href="{{url_for("blog_default")}}">All</a>
|
||||||
|
{% else %}
|
||||||
|
All
|
||||||
|
{% endif %}
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
-
|
{% if tag == title %}
|
||||||
<a href="{{url_for("tag", tag=tag)}}">{{ tag }}</a>
|
-
|
||||||
|
{{ tag }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
<a href="{{url_for("tag", tag=tag)}}">{{ tag }}</a>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if title %}
|
|
||||||
{{ title }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ol class="content-list">
|
<ol class="content-list">
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="post-text">
|
<div id="post-text">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
<h2 id="comment">Comments</h2>
|
<h2 id="comment">Comments</h2>
|
||||||
<div id="disqus_thread"></div>
|
<div id="disqus_thread"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
|
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
|
||||||
var disqus_shortname = 'thomasloven'; // required: replace example with your forum shortname
|
var disqus_shortname = 'thomasloven'; // required: replace example with your forum shortname
|
||||||
var disqus_identifier = '{{ page.url[:-1] }}';
|
var disqus_identifier = '{{ page.url[:-1] }}';
|
||||||
|
|
||||||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
/* * * DON'T EDIT BELOW THIS LINE * * */
|
||||||
(function() {
|
(function() {
|
||||||
@ -23,6 +23,6 @@
|
|||||||
</script>
|
</script>
|
||||||
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
|
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
|
||||||
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
|
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock page %}
|
{% endblock page %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user