background image

 

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 

 

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.cfunkcje.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 

background image

 

6. Kompilacja i scalanie programów C przez „make” 

Polecenie „make” szuka w bieżącym katalogu pliku tekstowego o nazwie „makefile” lub „Makefile” 
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) 

 

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