Skip to content

CE408-OSL/Experiment06

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

آزمایش ۶ - مدیریت حافظه

  • گروه شماره ۲۵
    • معین آعلی - ۴۰۱۱۰۵۵۶۱
    • ثمین اکبری - ۴۰۱۱۰۵۵۹۴

malloc و free

به کمک malloc حافظه ی مورد نیاز برای یک instance از این ساختار را تخصیص می‌دهیم. برای فیلدهای instance ایجاد شده مقادیری را اختصاص می‌دهیم و آن‌ها را چاپ می‌کنیم. در نهایت به کمک free حافظه‌ی گرفته شده را آزاد می‌کنیم:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct MyStruct {
    int a;
    int b;
    char name[20];
};

int main() {
    struct MyStruct* ptr = (struct MyStruct*) malloc(sizeof(struct MyStruct));

    if (ptr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    printf("%p\n", ptr);

    ptr->a = 10;
    ptr->b = 20;
    strcpy(ptr->name, "Moeein");

    printf("a: %d\n", ptr->a);
    printf("b: %d\n", ptr->b);
    printf("name: %s\n", ptr->name);

    free(ptr);

    return 0;
}

خروجی:

malloc return value

خروجی malloc در واقع آدرس آن instance در حافظه است.

مشاهده حافظه‌ی پردازه‌ها

حال به کمک دستور ps -o user,vsz,rss,pmem,fname -e می‌خواهیم وضعیت حافظه‌ی پردازه‌ها را بررسی کنیم:

ps -o user,vsz,rss,pmem,fname -e

ستون‌های مشخص شده:

  • ستون user : کاربری که سازنده این پردازه است.
  • ستون vsz : اندازه‌ی حافظه مجازی پردازه.
  • ستون rss : اندازه‌ی حافظه فیزیکی پردازه که روی رم قرار دارد.
  • ستون pmem : نشان می‌دهد که rss چند درصد از حافظه کل سیستم است.
  • ستون fname : نام پردازه را نشان می‌دهد.

اجزای حافظه

حافظه‌ی یک پردازه در معماری‌های امروزی به اجزای زیر تقسیم بندی می‌شود:

  • بخش text که کد زبان ماشین پردازه را نگه داری می‌کند.

  • بخش data که متغیر‌های سراسری پردازه در آن قرار می گیرد، برخی از متغیر‌ها دارای مقدار اولیه هستند و برخی خیر. دسته ی دوم در بخشی به نام bss واقع می‌شوند.

  • بخش heap که شامل حافظه‌هایی است که به صورت پویا اختصاص داده می‌شوند.

  • بخش stack که متغیر‌های محلی، پارامتر‌های توابع و مقادیر بازگشتی آن ها در آن قرار دارند.

memort parts

ابتدا به کمک دستور زیر،محل قرار‌گیری دستور ls را در فایل‌سیستم پیدا می‌کنیم:

which ls

حال به کمک دستور size بررسی می‌کنیم که کدهای آن به چه بخش‌هایی اختصاص دارند:

size

واحد اعداد فوق بایت هستند.

اشتراک حافظه

به کمک دستور ldd کتابخانه‌های مشترکی که توسط دستور ls استفاده شده است را مشاهده می‌کنیم:

ldd ls

همچنین خروجی برای برنامه‌ی nano :

ldd nano

آدرس‌های بخش‌های مختلف حافظه‌ی پردازه

کد داده‌شده در انتهای دستور man etext :

#include <stdio.h>
#include <stdlib.h>

extern char etext, edata, end; 

int main(void){
    printf("First address past:\n");
    printf("    program text (etext)      %10p\n", &etext);
    printf("    initialized data (edata)  %10p\n", &edata);
    printf("    uninitialized data (end)  %10p\n", &end);

    exit(EXIT_SUCCESS);
};

نتیجه اجرا:

man etext

واضحا مقدار end بزرگت‌تر از edata و آن هم بزرگ‌تر از etext است. پس ساختار بالا صحیح است.

نمادهای etext، edata و end متغیر معمولی نیستند، بلکه برچسب‌هایی (symbols) هستند که لینکر هنگام ساخت برنامه ایجاد می‌کند و به آدرس پایان بخش‌های مختلف حافظه برنامه اشاره دارند (etext پایان بخش کد، edata پایان بخش داده‌های مقداردهی‌شده و end پایان داده‌های مقداردهی‌نشده یا BSS). چون این نمادها توسط لینکر تعریف می‌شوند و نه در فایل سورس، در کد با extern اعلام می‌شوند تا کامپایلر بداند نوعشان چیست و هشدار ندهد. معمولاً نوع char برای آن‌ها انتخاب می‌شود تا بتوان با گرفتن آدرسشان و انجام عملیات روی پوینتر به راحتی اندازه‌ی بخش‌های حافظه را محاسبه کرد.

حال می‌خواهیم با استفاده از سیستم‌کال sbrk ادرس انتهای هیپ را مشاهده کنیم. سپس با malloc مقداری حافظه اختصاص می‌دهیم و تغییرات انتهای هیپ را چاپ می‌کنیم:

#include <iostream>
#include <unistd.h> 
#include <cstdlib>  

int main() {
    int* k = (int*)malloc(1024);

    int* ptr = (int*)sbrk(0);

    int diff = ptr - k;

    printf("start of program : %p\nafter malloc : %p\ndifference : %d\n", k, ptr, diff);

    free(k); 
    return 0;
}

نتیجه‌ی اجرا:

heap

سپس تابع بازگشتی‌ای می‌نویسیم که خودش را تا عمق ۱۰۰ صدا بزند و هر بار یک متغیر لوکال داخل خودش بسازد و آدرس آن را چاپ کند.

#include <stdio.h>

void recursive(int depth, int max_depth) {
    int i;

    printf("depth: %d, address: %p\n", depth, (void*)&i);

    if (depth < max_depth) {
        recursive_func(depth + 1, max_depth);
    }
}

int main(void) {
    recursive(1, 100);
    return 0;
}

نتیجه:

recursive

از آنجا که stack در بسیاری از معماری‌ها (مثل x86/x86_64) به سمت آدرس‌های پایین‌تر رشد می‌کند، آدرس متغیر i در هر بار فراخوانی کمتر از دفعه قبلی خواهد شد.

About

OS Lab - Experiment06

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published