فصل سوم : شروع به کار با زبان C
چهارشنبه, ۱۱ آذر ۱۳۹۴، ۰۸:۳۶ ب.ظ
۳ شروع به کار با زبان C
این فصل به شما نشان می دهد که چگونه بجای استفاده از زبان اسمبلی از زبان C بعنوان زبان برنامه نویسی سیستم عاملتان استفاده کنید. زبان اسمبلی ابزار بسیار خوبی برای تعامل با پردازنده است و کنترلی حداکثری برروی تمام ابعاد کد را ارائه می¬دهد. بهرروی حداقل برای مولفان، زبان C زبانی است که برای استفاده بسیار مناسب تر است. در نتیجه، علاقمندیم که از C حداکثر استفاده ممکن را بکنیم و نیز در موارد اضطرار از کدهای اسمبلی در آن استفاده کنیم.
۱-۳ راه اندازی پشته
یکی از پیش نیازها برای استفاده از C پشته می باشد، بدلیل اینکه تمام برنامه های بزرگ C از پشته استفاده می کنند. راه اندازی پشته سخت تر از این نیست که کاری کنیم تا ثبات esp به انتهای یکی از نواحی حافظه آزاد است (بیاد داشته باشید که پشته در کامپیوترهای x86 به آدرس پائین تر رشد پیدا می کند) که بدرستی تراز شده باشد. (تراز بر 4 بایت از نظر کارایی بهتر پیشنهاد می شود). ما می توانیم esp را به نقطه ای تصادفی در حافظه اشاره دهیم به این دلیل که تنها چیزی که در حافظه وجود دارد GRUB، BIOS و هسته سیستم عامل و برخی از دستگاه های ورودی خروجی نگاشت شده به حافظه است. این ایده، ایده خوبی نیست – ما نمی دانیم چه میزان از حافظه موجود است یا اینکه esp به ناحیه ای اشاره دارد که توسط برنامه دیگری در حال استفاده کردن از آن است. ایده بهتر این است که از بخش رزرو شده حافظه آماده نشده در بخش bss موجود در فایل ELF هسته استفاده کنیم. بهتره که بجای استفاده از بخش داده به منظور کاهش حجم فایل اجرایی هسته از بخش bss استفاده کنیم. از آنجایی که GRUB قالب ELF را می فهمد، GRUB می تواند هرمیزان حافظه رزرو شده در بخش bss را در زمان بارگذاری سیستم عامل اختصاص دهد.
شبه دستور resb در NASM را می تواند برای استفاده از تعریف داده های آماده سازی نشده استفاده کرد :
KERNEL_STACK_SIZE equ 4096 ; size of stack in bytesنیاز نیست که درباره استفاده از حافظه آماده سازی نشده برای پشته نگران باشید، به این خاطر که ممکن نیست بتوانیم (بدون تغذیه دستی اشاره گر)یک محل از پشته را بخوانیم که هنوز در آن نوشته نشده است. یک برنامه (درست) نمی تواند عنصری را که از پشته pop کند که هرگز آن را به پشته push نکرده باشد. در نتیجه، محل های حافظه ی پشته همواره باید قبل از آنکه خوانده شوند، نوشته شوند.
section .bss
align 4 ; align at 4 bytes
kernel_stack: ; label points to beginning of memory
resb KERNEL_STACK_SIZE ; reserve stack for the kernel
پس اشاره گر پشته باید از طریق اشاره esp به انتهای kernel_stack در حافظه اشاره کنند:
mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
; stack (end of memory area)
۲-۳ فراخوانی کدهای C از درون کد اسمبلی
گام بعدی این است که یک تابع C را از درون کد اسمبلی مان فراخوانی کنیم. قرادادهای بسیار گوناگونی در ارتباط با چگونگی فراخوانی کدهای C از کدهای اسمبلی وجود دارد. این کتاب از قرارداد فراخوانی cdecl استفاده می کند زیرا این قراداد توسط GCC بکار گرفته شده است. قرارداد فراخوانی cdecl می گوید که آرگومان های یک تابع باید از طریق پشته به تابع ارسال شوند (برروی ماشین های x86). آرگومان های تابع باید به ترتیب از راست به چپ به پشته push شوند، به این شکل، شما ابتدا سمت راست ترین آرگومان را به پشته push می کنید. مقدار بازگشتی یک تابع باید در ثبات eax قرار گیرد. کد زیر یک مثال را نشان می دهد : /* The C function */نحوه فراخوانی کد بالا در اسمبلی :
int sum_of_three(int arg1, int arg2, int arg3)
{
return arg1 + arg2 + arg3;
}
; The assembly code
external sum_of_three ; the function sum_of_three is defined elsewhere
push dword 3 ; arg3
push dword 2 ; arg2
push dword 1 ; arg1
call sum_of_three ; call the function, the result will be in eax
۱-۲-۳ بسته بندی ساختارها
در سرتاسر این کتاب، شما همواره با "پیکربندی بایت ها" که مجموعه ای از بیت ها به ترتیبی مشخص هستند مواجه خواهید بود. در زیر مثالی با 32 بیت آورده شده : Bit: | 31 24 | 23 8 | 7 0 |بجای استفاده از اعداد صحیح بدون علامت (unsigned integer) بسیار بهتر است که برای کنترل چنین پیکربندی هایی از قرارداد "ساختارهای بسته بندی شده" استفاده کنید :
Content: | index | address | config |
struct example {وقتی در مثال قبلی از struct استفاده کردیم هیچ تضمینی به ما داده نشد که طول این ساختار دقیقاً 32 بیت باشد – کامپایلر می تواند به دلایل مختلف مقداری فاصله گذاری بین عناصر انجام دهد، بعنوان مثال جهت افزایش سرعت دسترسی به عناصر یا به منظور الزامات سخت افزاری سخت افزار و/یا کامپایلر. وقتی که از یک struct برای نمایش بایت های پیکربندی استفاده کنیم، مهم است که کامپایلر هیچ فاصله گذاری انجام ندهد زیرا struct به طور اتفاقی به شکل یک مقدار عدد صحیح 32 بیتی توسط سخت افزار رفتار خواهد کرد/ مشخصه packed را می توان به منظور مجبور کردن GCC به عدم فاصله گذاری بکار گرفت :
unsigned char config; /* bit 0 - 7 */
unsigned short address; /* bit 8 - 23 */
unsigned char index; /* bit 24 - 31 */
};
struct example {به یاد داشته باشید که ((attribute__((packed__ بخشی از C استاندارد نیست ممکن است در همه ی کامپایلرها کار نکند.
unsigned char config; /* bit 0 - 7 */
unsigned short address; /* bit 8 - 23 */
unsigned char index; /* bit 24 - 31 */
} __attribute__((packed));
۳-۳ کامپایل کردن کد C
زمانیکه کد C را برای سیستم عامل کامپایل می کنید، بسیاری از پرچم های GCC لازم است که به دستور کامپایل اضافه شوند. انجام این کار به این دلیل است که کد C نباید حضور کتابخانه های استاندارد فرض کند، به این خاطر که در سیستم عامل ما هیچ کتابخانه استانداردی وجود ندارد. برای اطلاع از پرچم ها، راهنمای (manual) مربوط به GCC را مطالعه کنید.پرچم های استفاده شده برای کدهای C به شکل زیر هستند :
-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfilesمثل همیشه، زمانی که برنامه ای به زبان C می نویسید ما پیشنهاد می کنیم که تمام هشدارها را فعال کنید و با آن ها بمانند خطاها برخورد کنید :
-nodefaultlibs
-Wall -Wextra -Werrorحال شما می¬توانید در فایلی به نام kmain.c تابعی با نام kmain ایجاد کنید که بتوانید از طریق Loader.s آن را فراخوانی کنید. در این لحظه از آموزش احتمالاً kmain به هیچ آرگومانی نیاز نخواهد داشت (امّا در فصول بعدی خواهد داشت).
۴-۳ ابزارهای ساختن
در این لحظه احتمالاً زمان خوبی برای راه اندازی برخی ابزارهای ساختن برای ساده کردن پروسه کامپایل و آزمایش و اجرای سیستم عامل وجود داشته باشد. سیستم های ساختن فراوانی وجود دارد امّا پیشنهاد می کنیم که از make استفاده کنید. یک makefile ساده برای سیستم عامل می تواند به شکل زیر باشد : OBJECTS = loader.o kmain.oمحتوای شاخه کاری شما در حال حاضر باید به شکل زیر در آمده باشد :
CC = gcc
CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \
-nostartfiles -nodefaultlibs -Wall -Wextra -Werror -c
LDFLAGS = -T link.ld -melf_i386
AS = nasm
ASFLAGS = -f elf
all: kernel.elf
kernel.elf: $(OBJECTS)
ld $(LDFLAGS) $(OBJECTS) -o kernel.elf
os.iso: kernel.elf
cp kernel.elf iso/boot/kernel.elf
genisoimage -R \
-b boot/grub/stage2_eltorito \
-no-emul-boot \
-boot-load-size 4 \
-A os \
-input-charset utf8 \
-quiet \
-boot-info-table \
-o os.iso \
iso
run: os.iso
bochs -f bochsrc.txt -q
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
%.o: %.s
$(AS) $(ASFLAGS) $< -o $@
clean:
rm -rf *.o kernel.elf os.iso
.حالا شما باید قادر باشید که که سیستم عامل را با یک دستور ساده make run اجرا کنید، این دستور هسته را کامپایل می کند و آن را برروی مقلّد bochs بوت می کند (به همانگونه که در فایل makefile بالا تعریف شده است).
|-- bochsrc.txt
|-- iso
| |-- boot
| |-- grub
| |-- menu.lst
| |-- stage2_eltorito
|-- kmain.c
|-- loader.s
|-- Makefile
۵-۳ مطالعات بعدی
کتاب زبان برنامه نویسی C نوشته ریچی و کرنیگان، ویراست دوّم، منبعی گرانبها برای یادگیری تمام جنبه های زبان C است.- ۹۴/۰۹/۱۱