برنامه نویسی

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 قطعه کد متفاوت از دو رشته مختلف نوشته ایم. در مرحله بعد، نحوه اجرای یک راه حل کاربردی تر را بررسی خواهیم کرد تا ایجاد آن و منتظر ماندن برای موضوعات آسان شود.

کد این آموزش در این مخزن موجود است. مخزن کد با هر قسمت جدید از سری به روز می شود.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا