1
Politechnika Krakowska im. T. Kościuszki
INSTYTUT INFORMATYKI STOSOWANEJ
Środowisko LINUX – Kompilacja programów języka C w systemie Linux
Systemy
Operacyjne
LABORATORIUM
2 x 45min
1. Cel
Celem niniejszego ćwiczenia jest zapoznanie się z podstawami kompilacji programów,
których kod źródłowy jest napisany w języku C.
2. Wstęp
Nazwa kompilacja, na co dzień jest używana w kontekście tłumaczenia z języka wyższego
poziomu na język niższego poziomu. W Linuksie kompilację kodu można przeprowadzić za
pomocą kompilatora gcc wywoływanego z linii poleceń lub też za pomocą polecenia make.
Narzędzie make służy do zarządzania kompilacją projektów składających się z wielu plików
źródłowych. Aby używać make należy napisać skrypt o nazwie Makefile lub makefile, w
którym opisane są: zależności pomiędzy plikami źródłowymi i plikami wynikowymi, sposób
tworzenia plików wynikowych z plików źródłowych. Następnie przy pomocy polecenia make
kompilujemy projekt. Make usprawnia kompilację, gdyż samodzielnie decyduje, które z
plików źródłowych mają być przekompilowane (sprawdzając daty ostatniej modyfikacji).
3. Przygotowanie systemu CentOS 5.0
Zanim przystąpimy do wpisywania kodu źródłowego programu i jego kompilacji należy
pobrać pakiet kompilatora gcc
Dokonujemy tego poleceniem:
yum install gcc
4. Kompilacja programu C w linii poleceń
Edycji kodu źródłowego należy dokonać przy użyciu edytora vim. Poniżej zamieszczony jest
kod źródłowy programu licz0.c:
#include
<stdio.h>
double
kwadrat(
double
x)
{
return
(x)*(x);
}
int
main(
int
argc,
char
* argv[])
{
double
liczba, kw_liczby;
printf(
"Podaj liczbe: "
);
scanf(
"%lf"
, &liczba);
kw_liczby=kwadrat(liczba);
printf(
"Jej kwadrat to: %lf\n"
, kw_liczby);
return
0;
}
Kompilowanie plików źródłowych C do postaci plików wykonywalnych wymaga użycia opcji „-o”.
gcc -o <nazwa_programu>.exe <nazwa_pliku>.c
2
Jest to skrótowa składnia, tylko dla naszej wygody. W rzeczywistości są to 2 oddzielne procesy:
kompilacja i linkowanie. Np. polecenie gcc –c licz0.c daje plik binarny: licz0.o, zawierający tylko nasz
kod, skompilowany do języka maszyny.
Polecenie gcc –o licz0.exe licz0.o linkuję (łączy) wszystkie funkcje znajdujące się w pliku ‘licz.o’, z
funkcjami biblioteki standardowej i tworzy plik wykonywalny: licz.exe.
W wyniku otrzymuje się plik wykonywalny, który wykonujemy po wpisaniu w linii poleceń: ./licz0.exe
5. Kompilacja i scalanie programu C w linii poleceń
licz1.c:
#include
<stdio.h>
double
kwadrat(
double
x);
double
szescian(
double
x);
int
dodaj(
int
a,
int
b);
void
powitaj(
char
* dopisek);
int
main(
int
argc,
char
* argv[])
{
double
liczba, kw_liczby;
powitaj(
"matematyki"
);
printf(
"Podaj liczbę: "
);
scanf(
"%lf"
, &liczba);
kw_liczby=kwadrat(liczba);
printf(
"Jej kwadrat to %lf\n"
, kw_liczby);
return
0;
}
funkcje.c:
double
kwadrat(
double
x)
{
return
(x)*(x);
}
powitanie.c:
#include
<stdio.h>
void
powitaj(
char
* dopisek)
{
printf(
"Witaj w swiecie %s\n"
, dopisek);
}
Pliki licz1.c, funkcje.c i powitanie.c należy teraz skompilować (przetłumaczyć), tak jak
zrobiono to w punkcie 5, a następnie zlinkować (scalić).
Scalanie plików obiektów w program wykonywalny wymaga użycia opcji „-o”
gcc –o <nazwa_celu>.exe <nazwa_pliku1>.o <nazwa_pliku2>.o …
<nazwa_celu> oznacza nazwę pliku wykonywalnego, która może, ale nie musi mieć żadnego
konkretnego rozszerzenia (takiego jak np. „exe” w systemie Windows).
W wielu systemach uniksowych można połączyć kompilację i scalanie plików w jednym poleceniu:
gcc –o <nazwa_celu>.exe <nazwa_pliku1>.c <nazwa_pliku2>.c …
Jeśli plik nagłówka (.h) nie znajduje się w bieżącym katalogu ani w żadnym z katalogów zawierających
nagłówki standardowych bibliotek to przy kompilacji stosowana jest opcja –I <katalog_naglowka> .
(duże 'i')
gcc –c –I <katalog_naglowka> <nazwa_pliku>.c
3
6. Kompilacja i scalanie programów C przez „make”
Polecenie „make” szuka w bieżącym katalogu pliku tekstowego o nazwie „makefile” lub „Makefile” w
podanej kolejności. Plik ten zawiera reguły opisujące dla „make”, co budować (wykonywać) i w jaki
sposób. Reguły pliku make mają następującą formę ogólną:
<zmienna>:=<polecenie lub warto
ść>
[…]
<nazwa_celu>: <zale
żność> [<zależność>] […]
<tabulator> <polecenie>
[<tabulator> <polecenie>]
[…]
Cel jest plikiem binarnym(wykonywalnym) lub obiektowym (.o), który chcemy utworzyć. Polecenia są
krokami takimi jak wywołania kompilatora lub poleceń powłoki koniecznymi dla utworzenia celu. Jeśli
cel nie istnieje „make” go buduje zgodnie z poleceniem. W przeciwnym razie porównuje daty
tworzenia celu z datami jego zależności. Jeśli są one późniejsze niż cel (przynajmniej jedna) cel
podlega przebudowaniu, bo zmienił się jakiś kod.
# przykaldowy plik Makefile
CC = gcc
CFLAGS = -O1
Licz1.exe : licz1.o funkcje.o powitanie.o
$(CC) $(CFLAGS) -o $@ $?
liczh.o : licz1.c
$(CC) $(CFLAGS) -c $?
funkcje.o : funkcje.c
$(CC) $(CFLAGS) -c $?
powitanie.o : powitanie.c
$(CC) $(CFLAGS) -c $?
clean:
rm
*.o
Wybrane zmiene automatyczne stosowane w plikach “make”:
$@ -symboliczna nazwa pliku celu w regule
$* -rdzeń nazwy pliku (bez rozszerzenia po kropce)
$< -nazwa pliku pierwszej zależności od reguły
$^ -lista wszystkich zależności w regule
$? - lista zależności nowszych niż cel
Zmienne lokalne w plikach „make” są przypisywane różnym poleceniom na początku skryptu.
Aby wytworzyć kod produkcyjny korzystamy z ustawienia 3 poziomów optymalizacji poprzez opcję „-
Ox” (x=0,1,2.3). Poziom „-O2” jest odpowiedni dla większości programów.
W jednym pliku „Makefile” można definiować wiele różnych celów – kompilować i scalać wiele
programów. Wywołanie „make” odbywa się z odpowiednim parametrem stanowiącym nazwę celu i
może zawierać opcje:
make [-<opcje>] <nazwa_celu lub plik_wykonywalny>
Wybrane opcje polecenia “make”(nie muszą występować):
-n : polecenia są składane i wyświetlane, ale nie wykonywane (dobre do testów)
-I <katalog> : katalog do poszukiwania plików make poza katalogiem bieżącym (duże 'i')
-s : (silent) make nie wypisuje poleceń na ekranie
-f <plik> : nazwa pliku make inna niż „makefile” lub „Makefile”
-k : nie przerywa działania jeśli nie uda się zbudować jednego z celów
-d : (debug) wyświetlane są informacje debugowania
-W<plik> : wykonuje się tak jakby wymieniony plik był zmodyfikowany (do testowania)
4
7. Kompilacja jądra Linux
W wersjach stabilnych źródeł jądra Linux środkowa liczba jest liczbą parzystą (np. 2.2.14), a w
wersjach rozwojowych jest to liczba nieparzysta.
Standardowo każda, świeżo zainstalowana dystrybucja systemu Linux posiada już rozpakowane
źródła w katalogu /usr/src/linux. Wersję najnowszą (zarówno stabilną jak i rozwojową)
najprościej można pobrać z Internetu. Pod adresem http://www.kernel.org/ znajduje się strona,
gdzie dostępne są wszystkie wersje jądra systemowego. Gdy źródła wybranej wersji jądra zostaną już
w całości pobrane na lokalny dysk twardy, należy zalogować się jako administrator (root) i je
rozpakować. Następnie wystarczy przekopiować plik ze źródłami do katalogu /usr/src. Jeżeli są to
źródła, których rozszerzeniem jest *.tar.gz, wydać z konsoli polecenie:
tar –zxvf linux-pobrana.wersja.jądra.tar.gz
Po rozpakowaniu źródeł, w katalogu /usr/src powinien pojawić się nowy katalog:
linux-pobrana.wersja.jądra.
Kolejnym krokiem jest utworzenie dowiązania symbolicznego dla katalogu ze źródłami nowej wersji
jądra. W tym celu należy wejść do katalogu /usr/src i wydać polecenie:
ln –s linux-pobrana.wersja.jadra linux
Jego utworzenie jest potrzebne dla kompilatora gcc, gdyż podczas kompilacji jądra, gcc odwołuje się
do katalogu /usr/src/linux, gdzie standardowo powinny znajdować się nagłówki jądra.
Aby dokonać konfiguracji wpisujemy polecenie:
make menuconfig (alternatywa: make xconfig, make config)
Konfiguracja odbywa się poprzez zaznaczenie potrzebnych opcji dzięki trzem reprezentacjom
graficznym:
<*> - podana opcja zostanie wdrążona do jądra na stałe,
< > - podana opcja nie zostanie wdrążona do jądra,
<M> - podana opcja zostanie wdrążona do systemu jako moduł.
Wszystkie wybrane opcje są zapisywane w pliku konfiguracyjnym „.config” znajdującym się w
katalogu ze źródłami jądra systemowego.
Po zakończeniu konfiguracji potrzebnych opcji dla jądra systemowego należy je skompilować.
Umożliwiają to następujące komendy:
make dep - sprawdzenie zależności,
make clean - usunięcie pozostałości po starej kompilacji jądra,
make bzImage - kompilacja jądra wraz z zapisaniem go w katalogu /usr/src/linux/arch/i386/boot,
make modules - kompilacja modułów,
make modules_install - instalacja modułów i przekopiowanie ich do katalogu
/lib/modules/pobrana.wersja.jądra
Jądro znajduje się w katalogu usr/src/linux/arch/i386/boot/bzImage i należy je zainstalować.
Aby je zainstalować należy przekopiować plik bzImage do katalogu /boot za pomocą polecenia:
cp /usr/src/linux/arch/i386/boot/bzImage /boot/vmlinuz-<wersja.jądra>
Kolejnym krokiem jest przekopiowanie do katalogu /boot pliku System.map:
cp /usr/src/linux/System.map /boot/System.map-<wersja.jądra>
Należy również przekopiować do katalogu /boot plik initrd-<wersja.jądra>.img
Aby uruchomić system GNU/Linux z nowym jądrem należy dodać wpis do pliku konfiguracyjnego
programu grub, który znajduje się w katalogu /boot/grub.conf.
title <nazwa> (wersja_jądra)
root
(hd0,0)
kernel
/vmlinuz-<wersja.jądra> ro root=<partycja_systemowa>
initrd
/initrd-<wersja.jądra>.img