x64 Assembly: Multithreading از ابتدا قسمت 1: Hello World!

محتوای سریال
در این سری آموزش به نحوه پیاده سازی تکنیک های چند رشته ای در اسمبلی x86_64 از ابتدا بدون استفاده از کتابخانه استاندارد خواهیم پرداخت.
1- قسمت 1: سلام دنیا! از ابتدا
نوشتن یک برنامه ساده برای چاپ به خروجی استاندارد از دو فرآیند مختلف.
2- قسمت 2: نخ ها
پیاده سازی یک روش کاربردی برای ایجاد و انتظار برای موضوعات.
3- قسمت 3: حافظه مشترک
پیاده سازی توابع مدیریت حافظه و نشان دادن اینکه چگونه می توانیم حافظه را بین رشته ها به اشتراک بگذاریم.
4- قسمت 4: Mutexes
پیاده سازی mutex برای کنترل دسترسی به حافظه بین رشته ها.
پیش نیازها
برای دنبال کردن این آموزش به دانش اولیه در اسمبلی نیاز دارید. شما همچنین به یک محیط لینوکس نیاز دارید زیرا ما همه چیز را از ابتدا با استفاده از فراخوانی سیستم هسته پیاده سازی می کنیم.
ابزار
در این آموزش من از اسمبلر گنو استفاده می کنم gas
با intel syntax
در امتداد پیوند دهنده گنو.
استفاده از اسمبلرهای دیگر مانند MASM
یا NASM
خوب است، فقط مطمئن شوید که تفاوت های نحوی بین کدهای نشان داده شده در این سری از آموزش ها را با کدی که قصد دارید بنویسید بررسی کنید، به این لینک نگاهی بیندازید.
سلام دنیا! از ابتدا
از آنجایی که ما در برابر کتابخانه استاندارد (پیوند با -nostdlib
) ما به توابع معمولی مانند دسترسی نخواهیم داشت printf
یا malloc
و باید آنها را از ابتدا با استفاده از فراخوانی سیستم لینوکس x86_64 پیاده سازی کند.
ساختار پروژه
- Project Folder
|
|-- lib/ # The directory for our reusable code
| |
| |-- util.asm # utilities (print, malloc, etc..)
| |....
|
|-- hello_world/
|
| -- main.asm # Our code for Part 1
عملکرد چاپ
اکنون باید یک تابع ساده را برای چاپ در خروجی استاندارد پیاده سازی کنیم STDOUT
با استفاده از تماس سیستمی نوشتن
ما print
تابع 2 آرگومان خواهد گرفت: ptr
و len
ptr
: آدرس رشته (در رجیستر ارسال شده است rdi
)len
: طول رشته (در رجیستر ارسال شده است rsi
)
# lib/util.asm
.intel_syntax
.section .text
# print function that takes (ptr, len)
# as arguments (rdi, rsi)
.global print
print:
mov %rdx, %rsi # move the length to rdx
mov %rsi, %rdi # move the pointer (rdi) to rsi
mov %rax, 0x01 # write syscall on x64 Linux
mov %rdi, 0x01 # STDOUT file descriptor
syscall
ret
این تابعی که در بالا تعریف کردیم با استفاده از 2 آرگومان می گیرد rdi
و rsi
ثبات ها سپس رجیسترها را مجدداً مرتب می کنند تا فراخوانی شوند write
syscall در STDOUT
توصیف کننده فایل
حالا با دستور زیر آن را اسمبل کنیم:
$ as lib/util.asm -o lib/util.o
حالا باید یک فایل شی داشته باشیم lib/util.o
که برای دسترسی به توابع کاربردی خود به آن لینک خواهیم داد.
fork
تماس سیستمی
در سرتاسر این سری از فراخوانی سیستم فورک استفاده زیادی خواهیم کرد که اساساً به سیستم عامل میگوید یک فرآیند فرزند از فرآیند فعلی ایجاد کند، این فرآیند فرزند یک کپی دقیق از والد است و از دستورالعمل بعدی شروع به اجرا میکند. والدین.
نوشتن کد
# hello_world/main.asm
.intel_syntax
.global _start
.section .text
.extern print # Use our print function
_start:
# Call the 'fork' syscall
mov %rax, 0x39 # fork syscall on x64 Linux
syscall
cmp %rax, 0 # 'fork' will return 0 to the child process
je _child
_parent:
# Print 'Hello from parent!'
lea %rdi, [%rip + msg1]
mov %rsi, OFFSET msg1len
call print
jmp _exit
_child:
# Print 'Hello from child!'
lea %rdi, [%rip + msg2]
mov %rsi, OFFSET msg2len
call print
_exit:
# Call the 'exit' syscall
mov %rax, 0x3c # exit syscall on x64 Linux
mov %rdi, 0x0 # Exit code
syscall
.section .data
msg1:
.ascii "Hello from parent!\n"
msg1len = . - msg1
msg2:
.ascii "Hello from child!\n"
msg2len = . - msg2
در کد بالا 2 عملکرد مختلف داریم: _parent
که چاپ خواهد شد Hello from parent!
و خروج، و عملکرد _child
که چاپ خواهد شد Hello from child!
و خروج روی عملکرد اصلی _start
یک را خواهیم ساخت fork
فراخوانی سیستم که یک کپی از فرآیند جاری ایجاد می کند و یک مقدار را در ثبات ذخیره می کند rax
، این مقدار خواهد بود 0
در پردازش فرزند و PID پردازش فرزند در والدین خواهد بود، بنابراین ما از این اطلاعات استفاده خواهیم کرد. ما ارزش ثبت را با هم مقایسه خواهیم کرد rax
به صفر، اگر صفر باشد به عدد می پریم _child
تابع، اگر نه، ما به اجرای آن ادامه خواهیم داد _parent
تابع.
مونتاژ و پیوند
ما کد را جمع آوری می کنیم و با آن پیوند می دهیم util.o
فایل شی حاوی print
بدون پیوند دادن کتابخانه استاندارد با استفاده از کامدهای زیر عمل کنید:
$ as hello_world/main.asm -o hello_world/hello_world.o
$ ld hello_world/hello_world.o lib/util.o -o hello_world/hello_world.elf -nostdlib
اگر همه چیز خوب پیش برود باید یک فایل اجرایی داشته باشیم hello_world/hello_world.elf
و اگر آن را اجرا کنیم باید خروجی را ببینیم:
$ ./hello_world/hello_world.elf
Hello from parent!
Hello from child!
$
عالی! ما با موفقیت یک برنامه اسمبلی x86_64 را برای اجرای 2 قطعه کد متفاوت از دو رشته مختلف نوشته ایم. در مرحله بعد، نحوه اجرای یک راه حل کاربردی تر را بررسی خواهیم کرد تا ایجاد آن و منتظر ماندن برای موضوعات آسان شود.
کد این آموزش در این مخزن موجود است. مخزن کد با هر قسمت جدید از سری به روز می شود.