Zastowowanie assemblera w Linuksie
Projekt realizowany
w ramach cwiczen SO
autor: Adam Mazur

Spis tresci:

  1. Assembler czy C?
  2. Gdzie jest assembler w kodzie Linuxa?
  3. Co jest kodowane assemblerem?
    1. /include
    2. /include/asm-.../
    3. /arch
    4. /arch/.../boot
    5. /arch/.../kernel
    6. /arch/.../lib
    7. /arch/.../mm
    8. /arch/.../math-emu
  4. Jak jest kompilowany kod dla odpowiedniej platformy sprzetowej?
  5. Wykorzystane materialy




Wstep:
Assembler czy C?

Kod Linuxa jest w znacznej wiekszosci pisany w jezyku C. Wydawac by sie moglo, ze pisanie systemu operacyjnego w jezyku wysokiego poziomu nie jest najbardziej optymalnym i wydajnym ze wzgledu na dzialanie systemu pomyslem. Jest jednak jedna, powazna zaleta takiego rozwiazania - przenosnosc kodu. Jak powszechnie wiadomo, Linux moze byc skompilowany na wiekszosci platform sprzetowych bez koniecznosci zaopatrywania sie w osobny kod dla kazdej z platform.

Z drugiej jednak strony, w przypadku systemu operacyjnego potrzebujemy np. bezposredniego dostepu do zasobow sprzetowych, a tego, w obliczu zroznicowania platform i procesorow, jezyki wysokiego poziomu nie potrafia obsluzyc.

Z tego wlasnie powodu, niezbedna byla koniecznosc stworzenia czesci kodu Linuxa w jezykach niskiego poziomu - odpowiednich dla konkretnych procesorow.

Oto jak wyglada polaczenie uniwersalnego kodu w jezyku wysokiego poziomu (w tym przypadku C) z kodami ograniczonymi na dzialanie w specyficznych srodowiskach (assemblery roznych procesorow)


Czesc pierwsza:
GDZIE
jest assembler w kodzie Linuxa?

Oto listening niektorych katalogow zrodla Linuxa:


linux-2.4.18-z-patchami
 |__ arch
 |   |__ alpha
 |   |__ arm
 |   |__ cris
 |   |__ i386
 |   |__ ia64
 |   |__ m68k
 |   |__ mips
 |   |__ mips64
 |   |__ parisc
 |   |__ ppc
 |   |__ s390
 |   |__ s390x
 |   |__ sh
 |   |__ sparc
 |   |__ sparc64
 |__ crypto
 |__ drivers
 |__ fs
 |__ grsecurity
 |__ include
 |   |__ asm -> asm-i386
 |   |__ asm-alpha
 |   |__ asm-arm
 |   |__ asm-cris
 |   |__ asm-generic
 |   |__ asm-i386
 |   |__ asm-ia64
 |   |__ asm-m68k
 |   |__ asm-mips
 |   |__ asm-mips64
 |   |__ asm-parisc
 |   |__ asm-ppc
 |   |__ asm-s390
 |   |__ asm-s390x
 |   |__ asm-sh
 |   |__ asm-sparc
 |   |__ asm-sparc64
 |   |__ linux
 |   |__ math-emu
 |   |__ net
 |   |__ pcmcia
 |   |__ scsi
 |   |__ video
 |__ init
 |__ ipc
 |__ kdb
 |__ kernel
 |__ lib
 |__ mm
 |__ net
 |__ scripts
Czerwona czcionka zaznaczono katalogi w ktorych miesci sie kod Linux'a zorientowany na jedna, konkretna, platforme sprzetowa. Latwo mozna zauwazyc ze nazwy katalogow sa w prosty sposob powiazane z architerkuta procesora ktorej dotycza.

Ponadto kazdy z katalogow arch/[architektura] zaiwera m.in. podkatalogi:
linux-2.4.18-z-patchami
 |__ arch
 |   |__ [architektura]
 |   |   |__ boot - za wyjatkiem architektury m86k oraz parisc
 .   |   |__ kernel
 .   |   |__ lib
     .   |__ mm
     .   |__ math-emu - nie ma go tylko w arm, cris, parisc, s390x i sh
a czasem tez odpowiadajace nazwom konkretnym modelom platform sprzetowych, np. dla architektury m68k sa to m.in.:
         |__ amiga
         |__ atari
         |__ hp300
         |__ mac
         |__ sun

Pliki zrodlowe w powyzej zaznaczonych miejscach maja przewaznie trzy mozliwe rozszerzenia:
.c - kod w jezyku C
.S - kod w assemblerze, odpowiednim dla danej architektury procesora
.h - pliki naglowkowe, odwolujace sie zarowno do kodu w C, jak i kodu w assemblerze

Jak mozna sie przekonac przegladajac zrodla, pliki z rozszerzeniem .S nie beda wystepowaly tak czesto jak mozna by sie bylo tego spodziewac. Czasem dalo sie cos zapisac w czystym C, czasem autorzy ograniczali sie jedynie do wstawek assemblerowskich, ale chyba najczesciej kod assemblera jest umieszczany w plikach naglowkowych (szczegolnie jako krotkie funkcje deklarowane inline, lub makra wstawiajace tylko kilka instrukcji assemblera)


Czesc druga:
CO
jest kodowane assemblerem?


include/

W assemblerze zostaly zapisane przede wszystkim najmniejsze i najprostrze z funkcji, ktorych dzialanie jest uzaleznione w jakis sposob od mozliwosci platformy sprzetowej (np. operacje wejscia/wyjscia na portach), badz ktore nie wymagaly setek assemblerowskich instrukcji kodu, a ktore ze wzgledu na ich implementacje w jezyku procesora w znaczny sposob zwiekszaja szybkosc dzialania systemu (wystarczy popatrzec na makro current dla i386)

Funkcje te najczesciej sa pisane jako makra, lub ujmowane dyrektywa inline i czesto implementowane bezposrednio w plikach naglowkowych (w katalogu [linux source]/include/asm-[architektura])



include/asm-[architektura]

Ponizej wyszczegolnilem pliki ktore w roznych architekturach powtarzaja sie najczesciej. Umyslnie pomijalem pozycje ktore sluza tylko jako pliki pomocnicze, lub ktore nie mialy wiele wspolnego z kodem assemblera. Te wyszczegolnione zas, dodatkowo zostaly pogrupowane ,,tematycznie''

pliki optymalizujace predkosc:
bitops.h
implementuje podstawowe operacje na bitach, np. set_bit, test_and_clear_bit czy find_first_zero_bit
byteorder.h
definiuje funkcje ujednolicajace kolejnosc bajtow w liczbach wielobajtowych (roznicuje pomiedzy little endian a bid endian) - przydatne do obslugi sieci gdzie jest z gory ustalony porzadek bajtow
checksum.h
implementuje zliczanie sum kontrolnych, glownie a'propo obslugi protokolu IP
xor.h
implementuje instrukcje zwiekszajace predkosc przy wyliczaniu sum kontrolnych dla RAID-5 (wykorzystujac do tego np. technologie MMX w Intelach)
string.h
implementacja makr i czesci funkcji operujacych na string'ach (biblioteka string.h) i pamieci (np. memcpy)

Kilka przykladow:
/include/asm-i386/bitops.h (fragment)
/**
 * set_bit - Atomically set a bit in memory
 * @nr: the bit to set
 * @addr: the address to start counting from
 *
 * This function is atomic and may not be reordered.  See __set_bit()
 * if you do not require the atomic guarantees.
 * Note that @nr may be almost arbitrarily large; this function is not
 * restricted to acting on a single-word quantity.
 */
static __inline__ void set_bit(int nr, volatile void * addr)
{
	__asm__ __volatile__( LOCK_PREFIX
		"btsl %1,%0"
		:"=m" (ADDR)
		:"Ir" (nr));
}     
/include/asm-i386/string.h (fragment)
#define __HAVE_ARCH_STRLEN
static inline size_t strlen(const char * s)
{
int d0;
register int __res;
__asm__ __volatile__(
	"repne\n\t"
	"scasb\n\t"
	"notl %0\n\t"
	"decl %0"
	:"=c" (__res), "=&D" (d0) :"1" (s),"a" (0), "0" (0xffffffff));
return __res;
}     

operacje wspomagajace wspolbieznosc
atomic.h
zawiera operacje niepodzielne, jak np. atomic_add czy atomic_dec_and_test
semaphore.h
rwsem.h
definicje struktur semaforow systemowych (odpowiednio: zwyklych i do czytania/pisania) i niektorych operacji na nich
spinlock.h
definicja struktury spinlockow i implementacja niektorych operacji na nich
current.h
definiuje makro get_current
processor.h
implementuje m.in.:
- pobieranie aktualnych wartosci rejestrow czy wskaznika na wykonywana instrukcje (przydatne np. przy przelaczaniu procesow)
- procedury/makra/struktury obslugi watkow (np. thread_struct, start_thread, exit_thread
system.h
zawiera m.in. makra obslugujace obsluge przerwan (np. cli, sti, save_flags) oraz makra prepare_to_switch i switch_to

/include/asm-ia64/current.h
#ifndef _ASM_IA64_CURRENT_H
#define _ASM_IA64_CURRENT_H

/*
 * Copyright (C) 1998-2000 Hewlett-Packard Co
 * Copyright (C) 1998-2000 David Mosberger-Tang 
 */

/* In kernel mode, thread pointer (r13) is used to point to the
   current task structure.  */
register struct task_struct *current asm ("r13");

#endif /* _ASM_IA64_CURRENT_H */
     
/include/asm-i386/current.h
     
#ifndef _I386_CURRENT_H
#define _I386_CURRENT_H

struct task_struct;

static inline struct task_struct * get_current(void)
{
	struct task_struct *current;
	__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
	return current;
 }
 
#define current get_current()

#endif /* !(_I386_CURRENT_H) */
     
/include/asm-sparc/senaphore.h (fragment)
static inline void down(struct semaphore * sem)
{
	register volatile int *ptr asm("g1");
	register int increment asm("g2");

#if WAITQUEUE_DEBUG
	CHECK_MAGIC(sem->__magic);
#endif

	ptr = &(sem->count.counter);
	increment = 1;

	__asm__ __volatile__(
	"mov	%%o7, %%g4\n\t"
	"call	___atomic_sub\n\t"
	" add	%%o7, 8, %%o7\n\t"
	"tst	%%g2\n\t"
	"bl	2f\n\t"
	" nop\n"
	"1:\n\t"
	".subsection 2\n"
	"2:\n\t"
	"save	%%sp, -64, %%sp\n\t"
	"mov	%%g1, %%l1\n\t"
	"mov	%%g5, %%l5\n\t"
	"call	%3\n\t"
	" mov	%%g1, %%o0\n\t"
	"mov	%%l1, %%g1\n\t"
	"ba	1b\n\t"
	" restore %%l5, %%g0, %%g5\n\t"
	".previous\n"
	: "=&r" (increment)
	: "0" (increment), "r" (ptr), "i" (__down)
	: "g3", "g4", "g7", "memory", "cc");
}     

organizacja pamieci:
page.h
definiuje stale i makra do obslugi stron pamieci (np. PAGE_SIZE, clear_page)
pgtable.h
definiuje stale i makra do obslugi tablicy stron w pamieci
uaccess.h
("user access") definiuje makra i instrukcje dajace dostep procesom uzytkownika (z poza jadra) do pamieci (np. put_user, copy_from_user)

/include/asm-i386/uaccess.h (fragment)
      
 /*
 * Tell gcc we read from memory instead of writing: this is because
 * we do not write to any memory gcc knows about, so there are no
 * aliasing issues.
 */
#define __put_user_asm(x, addr, err, itype, rtype, ltype)	\
	__asm__ __volatile__(					\
		"1:	mov"itype" %"rtype"1,%2\n"		\
		"2:\n"						\
		".section .fixup,\"ax\"\n"			\
		"3:	movl %3,%0\n"				\
		"	jmp 2b\n"				\
		".previous\n"					\
		".section __ex_table,\"a\"\n"			\
		"	.align 4\n"				\
		"	.long 1b,3b\n"				\
		".previous"					\
		: "=r"(err)					\
		: ltype (x), "m"(__m(addr)), "i"(-EFAULT), "0"(err))
      
/include/asm-i386/pgtable.h (fragmencik)
      
/*
 * The Linux memory management assumes a three-level page table setup. On
 * the i386, we use that, but "fold" the mid level into the top-level page
 * table, so that we physically have the same two-level page table as the
 * i386 mmu expects.
 *
 * This file contains the functions and defines necessary to modify and use
 * the i386 page table tree.
 */   

inne:
unistd.h
zawiera numery funkcji systemowych, makra wolajace te funkcje (syscallX) a takze makra kilku instrukcji (m.in.: open, read, write, close, dup, execve, wait)
bugs.h
wykrywa niedociagniecia sprzetowe (np. na Intelu slynna wpadka z bledami przy dzieleniu)
io.h
implementuje obsluge wejscia/wyjscia, ale uwaga: to wejscie/wyjscie portow (w assemblerze Intela cos jak in i out).

/include/asm-i386/bugs.h (fragment)
      
/*
 * This used to check for exceptions.. 
 * However, it turns out that to support that,
 * the XMM trap handlers basically had to
 * be buggy. So let's have a correct XMM trap
 * handler, and forget about printing out
 * some status at boot.
 *
 * We should really only care about bugs here
 * anyway. Not features.
 */
static void __init check_fpu(void)
{
	if (!boot_cpu_data.hard_math) {
#ifndef CONFIG_MATH_EMULATION
		printk(KERN_EMERG "No coprocessor found and no math emulation present.\n");
		printk(KERN_EMERG "Giving up.\n");
		for (;;) ;
#endif
		return;
	}

/* Enable FXSR and company _before_ testing for FP problems. */
	/*
	 * Verify that the FXSAVE/FXRSTOR data will be 16-byte aligned.
	 */
	if (offsetof(struct task_struct, thread.i387.fxsave) & 15) {
		extern void __buggy_fxsr_alignment(void);
		__buggy_fxsr_alignment();
	}
	if (cpu_has_fxsr) {
		printk(KERN_INFO "Enabling fast FPU save and restore... ");
		set_in_cr4(X86_CR4_OSFXSR);
		printk("done.\n");
	}
	if (cpu_has_xmm) {
		printk(KERN_INFO "Enabling unmasked SIMD FPU exception support... ");
		set_in_cr4(X86_CR4_OSXMMEXCPT);
		printk("done.\n");
	}

	/* Test for the divl bug.. */
	__asm__("fninit\n\t"
		"fldl %1\n\t"
		"fdivl %2\n\t"
		"fmull %2\n\t"
		"fldl %1\n\t"
		"fsubp %%st,%%st(1)\n\t"
		"fistpl %0\n\t"
		"fwait\n\t"
		"fninit"
		: "=m" (*&boot_cpu_data.fdiv_bug)
		: "m" (*&x), "m" (*&y));
	if (boot_cpu_data.fdiv_bug)
		printk("Hmm, FPU with FDIV bug.\n");
}    


arch/[architektura]

Kod umieszczony w katalogu arch/[architektura] rozni sie znacznie od kodu w plikach naglowkowych. Przede wszystkim assembler nie wystepuje tu az tak czesto. Poza tym kod pisany assemblerem przewaznie nie jest juz krociutki i prosty, a czesto sa to dlugie i zmudnie pisane funkcje, ktorych nie sposob byloby zaimplementowac inaczej (np. funkcja startujaca system, kiedy jadro jeszcze nie zostalo zaladowane do pamieci)



arch/[architektura]/boot

W arch/[architektura]/boot zostal zakodowany loader Linuxa. Miesci sie tu procedura uruchamiajaca proces init. W zaleznosci od sprzetu, moze byc ona wpisana scisle w pierwszy sektor dysku, moze musiec rozpakowac dalsza czesc kodu, zaladowac ja do wczesniej przygotowanego ramdisku lub do konkretnego miejsca w pamieci, lub tez przetestowac mozliwosci zainstalowanego sprzetu (w architekturze Intela sa np. sprawdzane podstawowe mozliwosci karty graficznej)

/arch/i386/boot/video.S (fragmencik)
      
# Set the 80x25 mode. If already set, do nothing.
set_80x25:
	movw	$0x5019, force_size		# Override possibly broken BIOS
use_80x25:
#ifdef CONFIG_VIDEO_400_HACK
	movw	$0x1202, %ax			# Force 400 scan lines
	movb	$0x30, %bl
	int	$0x10
#else
	movb	$0x0f, %ah			# Get current mode ID
	int	$0x10
	cmpw	$0x5007, %ax	# Mode 7 (80x25 mono) is the only one available
	jz	st80		# on CGA/MDA/HGA and is also available on EGAM

	cmpw	$0x5003, %ax	# Unknown mode, force 80x25 color
	jnz	force3

st80:	cmpb	$0, adapter	# CGA/MDA/HGA => mode 3/7 is always 80x25
	jz	set80

	movb	%gs:(0x0484), %al	# This is EGA+ -- beware of 80x50 etc.
	orb	%al, %al		# Some buggy BIOS'es set 0 rows
	jz	set80
	
	cmpb	$24, %al		# It's hopefully correct
	jz	set80
#endif /* CONFIG_VIDEO_400_HACK */
force3:	DO_STORE
	movw	$0x0003, %ax			# Forced set
	int	$0x10
set80:	stc
	ret
    
/arch/sh/boot/compressed/head.S
      
/*
 *  linux/arch/sh/boot/compressed/head.S
 *
 *  Copyright (C) 1999 Stuart Menefy
 */

.text

#include 

	.global	startup
startup:
	/* Load initial status register */
	mov.l   init_sr, r1
	ldc     r1, sr

	/* First clear BSS */
	mov.l	end_addr, r1
	mov.l	bss_start_addr, r2
	mov	#0, r0
l1:
	mov.l	r0, @-r1
	cmp/eq	r1,r2
	bf	l1

	/* Set the initial pointer. */
	mov.l	init_stack_addr, r0
	mov.l	@r0, r15

	/* Decompress the kernel */
	mov.l	decompress_kernel_addr, r0
	jsr	@r0
	nop

	/* Jump to the start of the decompressed kernel */
	mov.l	kernel_start_addr, r0
	jmp	@r0
	nop
	
	.align	2
bss_start_addr:
	.long	__bss_start
end_addr:
	.long	_end
init_sr:
	.long	0x400000F0	/* Privileged mode, Bank=0, Block=0, IMASK=0xF */
init_stack_addr:
	.long	stack_start
decompress_kernel_addr:
	.long	decompress_kernel
kernel_start_addr:
	.long	_text+0x1000

	.align	9
fake_headers_as_bzImage:
	.word	0
	.ascii	"HdrS"		! header signature
	.word	0x0202		! header version number (>= 0x0105)
				! or else old loadlin-1.5 will fail)
	.word	0		! default_switch
	.word	0		! SETUPSEG
	.word	0x1000
	.word	0		! pointing to kernel version string
	.byte	0		! = 0, old one (LILO, Loadlin,
				! 0xTV: T=0 for LILO
				!       V = version
	.byte	1		! Load flags bzImage=1
	.word	0x8000		! size to move, when setup is not
	.long	0x100000	! 0x100000 = default for big kernel
	.long	0		! address of loaded ramdisk image
	.long	0		# its size in bytes
    


arch/[architektura]/kernel

W arch/[architektura]/kernel miesci sie kod podstawowych funkcji wykorzystywanych przez jadro, jak np. obsluga semaforow systemowych czy przelaczania procesora pomiedzy trybami jadra i uzytkownika.

W znacznej mierze uniknieto tu jednak dosc pracochlonnego i nieczytelnego zapisu w jezyku assemblera. Assembler wystepuje wiec zawsze tylko:

oraz czasami w innych pomocniczych plikach, zaleznie od architektury, ktore jednak czesto implementuja wyzej wymieniona funkcjonalnosc zamiast bezposrednio w plikach entry.S, head.S, lub nawet zamiast w plikach naglowkowych z include/asm/
/arch/i386/kernel/process.c (fragmencik)
/*
 * Create a kernel thread
 */
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
	long retval, d0;

	__asm__ __volatile__(
		"movl %%esp,%%esi\n\t"
		"int $0x80\n\t"		/* Linux/i386 system call */
		"cmpl %%esp,%%esi\n\t"	/* child or parent? */
		"je 1f\n\t"		/* parent - jump */
		/* Load the argument into eax, and push it.  That way, it does
		 * not matter whether the called function is compiled with
		 * -mregparm or not.  */
		"movl %4,%%eax\n\t"
		"pushl %%eax\n\t"		
		"call *%5\n\t"		/* call fn */
		"movl %3,%0\n\t"	/* exit */
		"int $0x80\n"
		"1:\t"
		:"=&a" (retval), "=&S" (d0)
		:"0" (__NR_clone), "i" (__NR_exit),
		 "r" (arg), "r" (fn),
		 "b" (flags | CLONE_VM)
		: "memory");
	return retval;
}    


arch/[architektura]/lib

Ciezko jednoznacznie okreslic specyfike implementacji zawartych w arch/[architektura]/lib. Sa tu przerozne funkcje, czesto pisane w assemblerze z czysto optymalizacyjnych powodow. W znacznej wiekszosci nazwy plikow odpowiadaja implementowanym przez nie funkcjom (np. strlen.S) lub nawet calym bibliotekom (np. string.S)

Wsrod kodow assemblera zawartych w tym podkatalogu, w zaleznosci od architktury, mozemy znalezc m.in. takie "roznosci" jak:


/arch/i386/lib/strstr.c
      
#include <linux/string.h>

char * strstr(const char * cs,const char * ct)
{
int	d0, d1;
register char * __res;
__asm__ __volatile__(
	"movl %6,%%edi\n\t"
	"repne\n\t"
	"scasb\n\t"
	"notl %%ecx\n\t"
	"decl %%ecx\n\t"	/* NOTE! This also sets Z if searchstring='' */
	"movl %%ecx,%%edx\n"
	"1:\tmovl %6,%%edi\n\t"
	"movl %%esi,%%eax\n\t"
	"movl %%edx,%%ecx\n\t"
	"repe\n\t"
	"cmpsb\n\t"
	"je 2f\n\t"		/* also works for empty string, see above */
	"xchgl %%eax,%%esi\n\t"
	"incl %%esi\n\t"
	"cmpb $0,-1(%%eax)\n\t"
	"jne 1b\n\t"
	"xorl %%eax,%%eax\n\t"
	"2:"
	:"=a" (__res), "=&c" (d0), "=&S" (d1)
	:"0" (0), "1" (0xffffffff), "2" (cs), "g" (ct)
	:"dx", "di");
return __res;
}  
/arch/i386/lib/mmx.c (fragment)
      
/*
 *	MMX 3DNow! library helper functions
 *
 *	To do:
 *	We can use MMX just for prefetch in IRQ's. This may be a win. 
 *		(reported so on K6-III)
 *	We should use a better code neutral filler for the short jump
 *		leal ebx. [ebx] is apparently best for K6-2, but Cyrix ??
 *	We also want to clobber the filler register so we dont get any
 *		register forwarding stalls on the filler. 
 *
 *	Add *user handling. Checksums are not a win with MMX on any CPU
 *	tested so far for any MMX solution figured.
 *
 *	22/09/2000 - Arjan van de Ven 
 *		Improved for non-egineering-sample Athlons 
 *
 */
 
void *_mmx_memcpy(void *to, const void *from, size_t len)
{
	void *p;
	int i;

	if (in_interrupt())
		return __memcpy(to, from, len);

	p = to;
	i = len >> 6; /* len/64 */

	kernel_fpu_begin();

	__asm__ __volatile__ (
		"1: prefetch (%0)\n"		/* This set is 28 bytes */
		"   prefetch 64(%0)\n"
		"   prefetch 128(%0)\n"
		"   prefetch 192(%0)\n"
		"   prefetch 256(%0)\n"
		"2:  \n"
		".section .fixup, \"ax\"\n"
		"3: movw $0x1AEB, 1b\n"	/* jmp on 26 bytes */
		"   jmp 2b\n"
		".previous\n"
		".section __ex_table,\"a\"\n"
		"	.align 4\n"
		"	.long 1b, 3b\n"
		".previous"
		: : "r" (from) );
		
	
	for(; i>0; i--)
	{
		__asm__ __volatile__ (
		"1:  prefetch 320(%0)\n"
		"2:  movq (%0), %%mm0\n"
		"  movq 8(%0), %%mm1\n"
		"  movq 16(%0), %%mm2\n"
		"  movq 24(%0), %%mm3\n"
		"  movq %%mm0, (%1)\n"
		"  movq %%mm1, 8(%1)\n"
		"  movq %%mm2, 16(%1)\n"
		"  movq %%mm3, 24(%1)\n"
		"  movq 32(%0), %%mm0\n"
		"  movq 40(%0), %%mm1\n"
		"  movq 48(%0), %%mm2\n"
		"  movq 56(%0), %%mm3\n"
		"  movq %%mm0, 32(%1)\n"
		"  movq %%mm1, 40(%1)\n"
		"  movq %%mm2, 48(%1)\n"
		"  movq %%mm3, 56(%1)\n"
		".section .fixup, \"ax\"\n"
		"3: movw $0x05EB, 1b\n"	/* jmp on 5 bytes */
		"   jmp 2b\n"
		".previous\n"
		".section __ex_table,\"a\"\n"
		"	.align 4\n"
		"	.long 1b, 3b\n"
		".previous"
		: : "r" (from), "r" (to) : "memory");
		from+=64;
		to+=64;
	}
	/*
	 *	Now do the tail of the block
	 */
	__memcpy(to, from, len&63);
	kernel_fpu_end();
	return p;
}  


arch/[architektura]/mm
arch/[architektura]/mm zawiera implementacje jednostki MMU. W znacznej wiekszosci jest ona pisana w C. Z assembler'a korzysta sie jedynie w architekturach arm, ppc, sh, sparc i sparc64 a i tam tylko w nielicznych przypadkach (np. przy implementacji kopiowania i czyszczenia calych stron pamieci)
/arch/sh/mm/clear_page.S
      
/* $Id: clear_page.S,v 1.1 2001/07/23 10:08:50 gniibe Exp $
 *
 * clear_page implementation of SuperH
 *
 * Copyright (C) 2001  Niibe Yutaka & Kaz Kojima
 *
 */

/*
 * clear_page
 * @to: P1 address
 *
 * void clear_page(void *to)
 */

/*
 * r0 --- scratch
 * r4 --- to
 * r5 --- to + 4096
 */
#include <linux/linkage.h>
ENTRY(clear_page)
	mov	r4,r5
	mov.w	.Llimit,r0
	add	r0,r5
	mov	#0,r0
	!
1:
#if defined(__sh3__)
	mov.l	r0,@r4
#elif defined(__SH4__)
	movca.l	r0,@r4
	mov	r4,r1
#endif
	add	#32,r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
	mov.l	r0,@-r4
#if defined(__SH4__)
	ocbwb	@r1
#endif
	cmp/eq	r5,r4
	bf/s	1b
	 add	#28,r4
	!
	rts
	 nop
.Llimit:	.word	(4096-28)
  


arch/[architektura]/math_emu

Do zaimplementowanych tu rozwiazan Linux odwoluje sie w przypadku gdy maszyna nie jest w stanie wykonac pewnego minimalnego dla Linuxa zestawu operacji arytmetycznych (np. przy architekturze Intela Linux potrafi symulowac na procesorze 386 instrukcje procesora 486, jak wyliczanie pierwiastka lub sinusa)

Znaczna wiekszosc zawartego tu kodu jest jednak pisana w C. Z assemblera korzysta sie okreslajac jedynie niektore z podstawowych funkcji, ktore specyfika danego procesora moze nieco uproscic (np. na Intelu mozna wykorzystac fakt ze mnozac 32-bitowe rejestry dostajemy wynik 64-bitowy - EDX:EAX)

/arch/sparc/math_emu/spf_util.h (fragmencik)
/* It's quite necessary to add this much assembler for the sparc.
   The default udiv_qrnnd (in C) is more than 10 times slower!  */
#define udiv_qrnnd(q, r, n1, n0, d) \
  __asm__ ("! Inlined udiv_qrnnd
	mov	32,%%g1
	subcc	%1,%2,%%g0
1:	bcs	5f
	 addxcc %0,%0,%0	! shift n1n0 and a q-bit in lsb
	sub	%1,%2,%1	! this kills msb of n
	addx	%1,%1,%1	! so this can't give carry
	subcc	%%g1,1,%%g1
2:	bne	1b
	 subcc	%1,%2,%%g0
	bcs	3f
	 addxcc %0,%0,%0	! shift n1n0 and a q-bit in lsb
	b	3f
	 sub	%1,%2,%1	! this kills msb of n
4:	sub	%1,%2,%1
5:	addxcc	%1,%1,%1
	bcc	2b
	 subcc	%%g1,1,%%g1
! Got carry from n.  Subtract next step to cancel this carry.
	bne	4b
	 addcc	%0,%0,%0	! shift n1n0 and a 0-bit in lsb
	sub	%1,%2,%1
3:	xnor	%0,0,%0
	! End of inline udiv_qrnnd"					\
	   : "=&r" ((USItype)(q)),					\
	     "=&r" ((USItype)(r))					\
	   : "r" ((USItype)(d)),					\
	     "1" ((USItype)(n1)),					\
	     "0" ((USItype)(n0)) : "%g1", "cc")
   
/arch/i386/math_emu/fpu_trig.c (fragmencik)
/*---------------------------------------------------------------------------*/
/* The following all require two arguments: st(0) and st(1) */

/* A lean, mean kernel for the fprem instructions. This relies upon
   the division and rounding to an integer in do_fprem giving an
   exact result. Because of this, rem_kernel() needs to deal only with
   the least significant 64 bits, the more significant bits of the
   result must be zero.
 */
static void rem_kernel(unsigned long long st0, unsigned long long *y,
		       unsigned long long st1,
		       unsigned long long q, int n)
{
  int dummy;
  unsigned long long x;

  x = st0 << n;

  /* Do the required multiplication and subtraction in the one operation */

  /* lsw x -= lsw st1 * lsw q */
  asm volatile ("mull %4; subl %%eax,%0; sbbl %%edx,%1"
		:"=m" (((unsigned *)&x)[0]), "=m" (((unsigned *)&x)[1]),
		"=a" (dummy)
		:"2" (((unsigned *)&st1)[0]), "m" (((unsigned *)&q)[0])
		:"%dx");
  /* msw x -= msw st1 * lsw q */
  asm volatile ("mull %3; subl %%eax,%0"
		:"=m" (((unsigned *)&x)[1]), "=a" (dummy)
		:"1" (((unsigned *)&st1)[1]), "m" (((unsigned *)&q)[0])
		:"%dx");
  /* msw x -= lsw st1 * msw q */
  asm volatile ("mull %3; subl %%eax,%0"
		:"=m" (((unsigned *)&x)[1]), "=a" (dummy)
		:"1" (((unsigned *)&st1)[0]), "m" (((unsigned *)&q)[1])
		:"%dx");

  *y = x;
}   



Czesc trzecia:
JAK
jest kompilowany kod dla odpowiedniej platformy sprzetowej?

Skoro istnieje wiele implementacji tych samych funkcji, ktore na dodatek roznia sie jezykiem w ktorym zostaly napisane (kazdy assembler jest inny), mozna sie zastanawiac jak podczas kompilacji wybrac ta odpowiednia implementacje.

W Linuxie rozwiazano to tworzac link symbiliczny [linux source]]/include/asm wskazujacy na katalog [linux source]]/include/asm-[architektura] w ktorym znajduja sie pliki naglowkowe charakterystyczne dla danej architektury. W ten sposob, w zrodlach nie zwiazanych z konkretna platforma sprzetowa, dyrektywa #include moze sie odwolywac do plikow naglowkowych z katalogu asm/, a dzialajacy system nakieruje kompilator na odpowiednia czesc kodu.

W zrodlach funkcjonuje rowniez definicja __[architektura]__ (np. __i386__) i w ten sposob na pomoca dyrektywy #ifdef (np. #ifdef __i386__) mozna odgrodzic fragment kodu przeznaczony na ten procesor od fragmentu przeznaczonego na inna maszyne. W szczegolnosci zas w "odgrodzonych" fragmentach kodu mozna zamiescic konkretnego assemblera.




Wykorzystane materialy

Korzystalem ze zrodel Linux'a w wersji 2.4.18 dostepnych w ramach labolatorium