কেন এই স্মৃতি ভক্ষণকারী সত্যই স্মৃতি খায় না?


150

আমি এমন একটি প্রোগ্রাম তৈরি করতে চাই যা ইউনিক্স সার্ভারে একটি মেমরির (OOM) পরিস্থিতি অনুকরণ করে। আমি এই অতি-সহজ মেমরি ভোজনটি তৈরি করেছি:

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

unsigned long long memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
void *memory = NULL;

int eat_kilobyte()
{
    memory = realloc(memory, (eaten_memory * 1024) + 1024);
    if (memory == NULL)
    {
        // realloc failed here - we probably can't allocate more memory for whatever reason
        return 1;
    }
    else
    {
        eaten_memory++;
        return 0;
    }
}

int main(int argc, char **argv)
{
    printf("I will try to eat %i kb of ram\n", memory_to_eat);
    int megabyte = 0;
    while (memory_to_eat > 0)
    {
        memory_to_eat--;
        if (eat_kilobyte())
        {
            printf("Failed to allocate more memory! Stucked at %i kb :(\n", eaten_memory);
            return 200;
        }
        if (megabyte++ >= 1024)
        {
            printf("Eaten 1 MB of ram\n");
            megabyte = 0;
        }
    }
    printf("Successfully eaten requested memory!\n");
    free(memory);
    return 0;
}

এটি নির্ধারিত মেমরিটি খায় memory_to_eatযা এখন ঠিক 50 গিগাবাইট র‌্যাম। এটি 1 এমবি দ্বারা মেমরি বরাদ্দ করে এবং ঠিক আরও বেশি বরাদ্দ করতে ব্যর্থ হয় ঠিক এমন পয়েন্টটি মুদ্রণ করে, যাতে আমি জানি যে এটি কোন সর্বোচ্চ মান খেতে পরিচালিত হয়েছিল।

সমস্যাটি হ'ল এটি কাজ করে। এমনকি 1 জিবি শারীরিক মেমরি সহ এমন একটি সিস্টেমে।

আমি যখন শীর্ষে যাব তখন আমি দেখতে পাচ্ছি যে প্রক্রিয়াটি 50 গিগাবাইট ভার্চুয়াল মেমরি খায় এবং আবাসিক মেমরির 1 এমবি এরও কম। মেমরি ইটার তৈরির কোনও উপায় আছে যা সত্যই এটি গ্রাস করে?

সিস্টেমের বৈশিষ্ট্য: লিনাক্স কার্নেল ৩.১16 ( দেবিয়ান ) সম্ভবত অদলবদল এবং ভার্চুয়ালাইজড ওভারকমিট সক্ষম (এটি কীভাবে পরীক্ষা করবেন তা নিশ্চিত নয়) with


16
হতে পারে আপনি আসলে এই স্মৃতি ব্যবহার করতে হবে (অর্থাত্ এটি লিখুন)?
এমএস

4
আমি মনে করি না যে সংকলক এটি অনুকূল করে, যদি এটি সত্য হয় তবে এটি 50 গিগাবাইট ভার্চুয়াল মেমরির বরাদ্দ করবে না।
পেটর

18
@ ম্যাগিশ আমি মনে করি এটি সংকলক নয় তবে ওএস অনুলিপি-অন-লিখনের মতো।
ক্যাডানিলুক

4
আপনি ঠিক বলেছেন, আমি এটিতে লেখার চেষ্টা করেছি এবং আমি আমার ভার্চুয়াল বাক্সটি সরিয়ে দিয়েছি ...
পেটর

4
মূল প্রোগ্রামটি আপনি যেমন প্রত্যাশা করেছিলেন তেমন আচরণ করবে যদি আপনি sysctl -w vm.overcommit_memory=2মূল হিসাবে কাজ করেন; দেখতে mjmwired.net/kernel/Documentation/vm/overcommit-accounting । দ্রষ্টব্য যে এটির অন্যান্য পরিণতিও হতে পারে; বিশেষত, খুব বড় প্রোগ্রাম (যেমন আপনার ওয়েব ব্রাউজার) সাহায্যকারী প্রোগ্রাম (যেমন পিডিএফ রিডার) স্প্যান করতে ব্যর্থ হতে পারে।
zwol

উত্তর:


221

আপনার যখন malloc()বাস্তবায়ন সিস্টেমের কার্নেল থেকে মেমরি অনুরোধ (একটি মাধ্যমে sbrk()অথবা mmap()সিস্টেম কল), কার্নেল শুধুমাত্র একটি নোট যে আপনি মেমরি অনুরোধ করেছেন এবং এটি আপনার অ্যাড্রেস স্পেস মধ্যে স্থাপন করা হবে যেখানে তোলে। এটি এখনও সেই পৃষ্ঠাগুলি ম্যাপ করে না

প্রক্রিয়াটি পরবর্তীকালে নতুন অঞ্চলে মেমরি অ্যাক্সেস করে, হার্ডওয়্যার একটি সেগমেন্টেশন ত্রুটি সনাক্ত করে এবং কার্নেলটিকে শর্তে সতর্ক করে। এর পরে কার্নেল পৃষ্ঠাটি তার নিজস্ব ডেটা কাঠামোতে সন্ধান করে এবং দেখতে পাচ্ছে যে সেখানে আপনার একটি শূন্য পৃষ্ঠা থাকা উচিত, সুতরাং এটি শূন্য পৃষ্ঠায় মানচিত্র করে (সম্ভবত প্রথম পৃষ্ঠা-ক্যাশে থেকে কোনও পৃষ্ঠা সরিয়ে ফেলা) এবং বাধা থেকে ফিরে আসে। আপনার প্রক্রিয়া বুঝতে পারে না যে এর কোনওটি ঘটেছে, কার্নেলের ক্রিয়াটি পুরোপুরি স্বচ্ছ (কর্নেল যখন কাজ করবে তখন স্বল্প বিলম্ব ব্যতীত)।

এই অপটিমাইজেশনটি সিস্টেম কলটিকে খুব দ্রুত ফিরে আসতে দেয় এবং সর্বাধিক গুরুত্বপূর্ণ, ম্যাপিংয়ের সময় এটি কোনও প্রক্রিয়া আপনার প্রক্রিয়াতে প্রতিশ্রুতিবদ্ধ হতে এড়াতে পারে। এটি প্রক্রিয়াগুলি বৃহত্তর বাফারগুলি সংরক্ষণ করতে সক্ষম করে যা তাদের কখনও কখনও খুব বেশি স্মৃতি স্মরণে রাখার ভয় ছাড়াই স্বাভাবিক পরিস্থিতিতে প্রয়োজন হয় না।


সুতরাং, আপনি যদি কোনও মেমরি ইটার প্রোগ্রাম করতে চান তবে আপনার বরাদ্দকৃত মেমরির সাথে আপনাকে অবশ্যই কিছু করতে হবে। এর জন্য, আপনাকে কেবল আপনার কোডে একটি লাইন যুক্ত করতে হবে:

int eat_kilobyte()
{
    if (memory == NULL)
        memory = malloc(1024);
    else
        memory = realloc(memory, (eaten_memory * 1024) + 1024);
    if (memory == NULL)
    {
        return 1;
    }
    else
    {
        //Force the kernel to map the containing memory page.
        ((char*)memory)[1024*eaten_memory] = 42;

        eaten_memory++;
        return 0;
    }
}

মনে রাখবেন যে প্রতিটি পৃষ্ঠার মধ্যে একক বাইটে লিখতে এটি পুরোপুরি যথেষ্ট (যা X86 এ 4096 বাইট রয়েছে)। এর কারণ কার্নেল থেকে কোনও প্রক্রিয়াতে সমস্ত মেমরি বরাদ্দ মেমরি পৃষ্ঠার গ্রানুলারিটিতে সম্পন্ন হয়, যার পরিবর্তে, এমন হার্ডওয়ারের কারণে যা ছোট গ্রানুলারিজে পেজিং দেয় না allow


6
এটির সাথে মেমরির প্রতিশ্রুতি দেওয়াও সম্ভব mmapএবং MAP_POPULATE(যদিও নোট করুন যে ম্যান পেজটি " লিনাক্স ২.6.২৩ " কেবলমাত্র ব্যক্তিগত ম্যাপিংয়ের জন্য সমর্থিত " এমএপ্পপলটেট ")।
টবি স্পাইট

2
এটি মূলত সঠিক, তবে আমি মনে করি পৃষ্ঠাগুলি সমস্ত পৃষ্ঠা-টেবিলগুলিতে উপস্থিত না হওয়ার পরিবর্তে শূন্য পৃষ্ঠায় ম্যাপ করা সমস্ত অনুলিপি রয়েছে। এজন্য আপনাকে প্রতিটি পৃষ্ঠায় কেবল পড়তে হবে না, লিখতে হবে। এছাড়াও, শারীরিক স্মৃতি ব্যবহার করার আরেকটি উপায় হ'ল পৃষ্ঠাগুলি লক করা। যেমন কল mlockall(MCL_FUTURE)। (এই রুট প্রয়োজন, কারণ ulimit -lএকটি ডিফল্ট Debian / Ubuntu- এর ইনস্টল উপর ব্যবহারকারী অ্যাকাউন্টের জন্য শুধুমাত্র 64kiB হয়।) আমি লিনাক্স 3.19 তে এটি চেষ্টা ডিফল্ট sysctl সঙ্গে vm/overcommit_memory = 0, এবং লক পৃষ্ঠাগুলি swap 'র / প্রকৃত RAM আপ ব্যবহার করুন।
পিটার কর্ডস

2
@ ক্যাড যদিও এক্স 86-64 দুটি বৃহত পৃষ্ঠার আকার (2 মাইবি এবং 1 জিআইবি) সমর্থন করে তবে লিনাক্স কার্নেল দ্বারা তাদের এখনও বেশ বিশেষ চিকিত্সা করা হয়। উদাহরণস্বরূপ, এগুলি কেবল সুস্পষ্ট অনুরোধে ব্যবহৃত হয়, এবং কেবলমাত্র যদি সিস্টেমটি তাদের অনুমতি দেওয়ার জন্য কনফিগার করা থাকে। এছাড়াও, 4 কিবি পৃষ্ঠাটি এখনও গ্রানুলারিটিতে রয়ে গেছে যেখানে মেমরি ম্যাপ করা যেতে পারে। এ কারণেই আমি মনে করি না যে বিশাল পৃষ্ঠাগুলি উল্লেখ করা উত্তরে কিছু যুক্ত করে।
মাস্টার -

1
@ অ্যালকিটেল হ্যাঁ, এটি হয়। কমপক্ষে লিনাক্সের ক্ষেত্রে, সম্ভবত এমন কোনও প্রক্রিয়া যা খুব বেশি মেমরি গ্রহণ করে তার মেমরির-ঘাতক দ্বারা malloc()কল করা তার কলগুলির চেয়ে বেশি হয়ে যায় null। এটি স্পষ্টভাবে মেমরি পরিচালনার এই পদ্ধতির খারাপ দিক। যাইহোক, এটি ইতিমধ্যে অনুলিপি-লেখার-ম্যাপিংসের অস্তিত্ব (ডাইনামিক লাইব্রেরি মনে করুন এবং fork()) যে কার্নেলের পক্ষে এটি জানা কতটা মেমরির প্রয়োজন হবে তা অসম্ভব করে তোলে। সুতরাং, যদি এটি মেমোরির উপর নির্ভর করে না, আপনি প্রকৃত সমস্ত শারীরিক মেমরি ব্যবহার করার অনেক আগে আপনি মানচিত্রযোগ্য মেমরিটি শেষ করে দিতেন।
মাস্টার

2
@ বিলবার্থ হার্ডওয়্যারটিতে আপনি কোনও পৃষ্ঠা ত্রুটি এবং সেগফল্ট বলবেন তার মধ্যে কোনও পার্থক্য নেই। হার্ডওয়্যারটি কেবল এমন একটি অ্যাক্সেস দেখতে পায় যা পৃষ্ঠার সারণীতে উল্লিখিত অ্যাক্সেস সীমাবদ্ধতা লঙ্ঘন করে এবং সেগমেন্টেশন ত্রুটির মাধ্যমে শর্তটিকে কার্নেলের সাথে সংকেত দেয়। এটি কেবলমাত্র সফ্টওয়্যার পক্ষের পরে সিদ্ধান্ত নেয় যে কোনও বিভাগ সরবরাহের মাধ্যমে বিভাগের ত্রুটিটি পরিচালনা করা উচিত (পৃষ্ঠা সারণীগুলি আপডেট করে), অথবা SIGSEGVপ্রক্রিয়াতে সংকেত সরবরাহ করা উচিত কিনা ।
মাস্টার

28

সমস্ত ভার্চুয়াল পৃষ্ঠাগুলি একই শূন্যযুক্ত শারীরিক পৃষ্ঠায় ম্যাপযুক্ত অনুলিপি শুরু করে। শারীরিক পৃষ্ঠাগুলি ব্যবহার করতে, আপনি প্রতিটি ভার্চুয়াল পৃষ্ঠায় কিছু লিখে এগুলি নোংরা করতে পারেন।

রুট হিসাবে চলতে থাকলে, পৃষ্ঠাগুলি বরাদ্দকালে কার্নেল তারের ব্যবহার করতে mlock(2)বা mlockall(2)তাদের নষ্ট না করে ব্যবহার করতে পারেন । (সাধারণ নন-রুট ব্যবহারকারীদের ulimit -lমধ্যে কেবলমাত্র ki৪কিবি রয়েছে))

অন্য অনেকে যেমন পরামর্শ দিয়েছিলেন, মনে হয় আপনি লিনাক্স না লিখলে লিনাক্স কার্নেল সত্যিই মেমরি বরাদ্দ করে না

কোডটির একটি উন্নত সংস্করণ, যা ওপি যা চেয়েছিল তা করে:

এটি পূর্ণসংখ্যা %ziমুদ্রণের জন্য মেমোরি_টো_িট এবং খাওয়া_মেমোরির প্রকারগুলির সাথে প্রিন্টফ ফর্ম্যাট স্ট্রিংয়ের সাথে মেলে না fix size_tকিবিতে খেতে থাকা মেমরির আকারটি allyচ্ছিকভাবে একটি কমান্ড লাইন আরগ হিসাবে নির্দিষ্ট করা যেতে পারে।

অগোছালো ডিজাইন গ্লোবাল ভেরিয়েবলগুলি ব্যবহার করে এবং 4k পৃষ্ঠাগুলির পরিবর্তে 1 কে বাড়ানো অপরিবর্তিত।

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

size_t memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
char *memory = NULL;

void write_kilobyte(char *pointer, size_t offset)
{
    int size = 0;
    while (size < 1024)
    {   // writing one byte per page is enough, this is overkill
        pointer[offset + (size_t) size++] = 1;
    }
}

int eat_kilobyte()
{
    if (memory == NULL)
    {
        memory = malloc(1024);
    } else
    {
        memory = realloc(memory, (eaten_memory * 1024) + 1024);
    }
    if (memory == NULL)
    {
        return 1;
    }
    else
    {
        write_kilobyte(memory, eaten_memory * 1024);
        eaten_memory++;
        return 0;
    }
}

int main(int argc, char **argv)
{
    if (argc >= 2)
        memory_to_eat = atoll(argv[1]);

    printf("I will try to eat %zi kb of ram\n", memory_to_eat);
    int megabyte = 0;
    int megabytes = 0;
    while (memory_to_eat-- > 0)
    {
        if (eat_kilobyte())
        {
            printf("Failed to allocate more memory at %zi kb :(\n", eaten_memory);
            return 200;
        }
        if (megabyte++ >= 1024)
        {
            megabytes++;
            printf("Eaten %i  MB of ram\n", megabytes);
            megabyte = 0;
        }
    }
    printf("Successfully eaten requested memory!\n");
    free(memory);
    return 0;
}

হ্যাঁ আপনি ঠিক বলেছেন, এটি কারণ ছিল, প্রযুক্তিগত পটভূমি সম্পর্কে নিশ্চিত না হলেও এটি বোধগম্য। যদিও এটি অদ্ভুত, এটি আমাকে ব্যবহার করতে পারে তার চেয়ে বেশি মেমরি বরাদ্দ করতে দেয়।
পেটর

আমি মনে করি ওএস স্তরের মেমরিটি কেবল তখনই ব্যবহার করা হয় যখন আপনি এটি লেখেন, যা ওএস বিবেচনা করে বিবেচনা করে যা আপনার তাত্ত্বিকভাবে সমস্ত স্মৃতিতে ট্যাব রাখে না, তবে কেবল এটিই যা আপনি আসলে ব্যবহার করেন।
ম্যাজিচ

@ পিতর মন যদি আমি আমার উত্তরটিকে সম্প্রদায় উইকি হিসাবে চিহ্নিত করি এবং আপনি ভবিষ্যতের ব্যবহারকারীর পঠনযোগ্যতার জন্য আপনার কোডটিতে সম্পাদনা করেন?
20:44

@ পেটার এটি মোটেই অদ্ভুত নয়। আজকের ওএসে মেমরি পরিচালনা এইভাবে কাজ করে। প্রক্রিয়াগুলির একটি প্রধান বৈশিষ্ট্য হ'ল তাদের পৃথক ঠিকানার স্থান রয়েছে যা তাদের প্রত্যেককে একটি ভার্চুয়াল ঠিকানার স্থান সরবরাহ করে সম্পন্ন হয়। x86-64 এমনকি 1 জিবি পৃষ্ঠাগুলির সাথে একটি ভার্চুয়াল ঠিকানার জন্য 48-বিট সমর্থন করে, সুতরাং, তাত্ত্বিকভাবে, প্রক্রিয়া অনুসারে কিছু টেরাবাইট মেমরি সম্ভব possible অ্যান্ড্রু টেনেনবাউম ওএস সম্পর্কে কিছু দুর্দান্ত বই লিখেছেন। আপনি যদি আগ্রহী হন তবে সেগুলি পড়ুন!
ক্যাডানিলুক

1
আমি "স্পষ্টত স্মৃতি ফাঁস" শব্দটি ব্যবহার করব না আমি বিশ্বাস করি না যে ওভার কমিট বা "লেখার উপর মেমরির অনুলিপি" র এই প্রযুক্তিটি মেমরির ফাঁসকে মোকাবেলা করার জন্য উদ্ভাবিত হয়েছিল।
পেটর

13

একটি বুদ্ধিমান অপ্টিমাইজেশন এখানে তৈরি করা হচ্ছে। রানটাইম আসলে মেমরিটি ব্যবহার না করা পর্যন্ত আসলে তা অর্জন করে না ।

memcpyএই অপ্টিমাইজেশানটি ছিন্ন করতে একটি সাধারণ যথেষ্ট simple (আপনি দেখতে পাবেন যে callocব্যবহারের পয়েন্ট অবধি মেমরির বরাদ্দকে অপ্টিমাইজ করে))


2
তুমি কি নিশ্চিত? আমি মনে করি যদি তার বরাদ্দের পরিমাণটি ভার্চুয়াল মেমরির সর্বাধিক পৌঁছে যায় তবে ম্যালোক ব্যর্থ হবে, তা যাই হোক না কেন। ম্যালোক () কীভাবে জানবেন যে কেউ স্মৃতি ব্যবহার করছে না ?? এটি পারে না, সুতরাং এটি অবশ্যই এসবিআরকে () বা তার ওএসের সমতুল্য যেকোন কিছু কল করতে হবে।
পিটার - মনিকা পুনরায় ইনস্টল করুন 20'15

1
আমি বেশ নিশ্চিত। (ম্যালোক জানেন না তবে রানটাইম অবশ্যই হবে) এটি পরীক্ষা করা তুচ্ছ (যদিও এই মুহূর্তে আমার পক্ষে সহজ নয়: আমি ট্রেনে আছি)।
বাথশেবা

@ বাথশেবা কি প্রতিটি পৃষ্ঠায় একটি করে বাইট লেখাও যথেষ্ট? ধরে নেওয়া যাক mallocআমার কি চমত্কার সম্ভবত বলে মনে হয় পৃষ্ঠা গণ্ডি উপর বরাদ্দ।
ক্যাডানিলুক

2
@ Doron এখানে কোন সংকলক জড়িত নেই। এটি লিনাক্স কার্নেলের আচরণ।
el.pescado

1
আমি মনে করি callocগ্লিবসি এমএমএপি (এমএপি_অ্যানোইমাইউস) শূন্য পৃষ্ঠাগুলি দেওয়ার সুযোগ নিয়েছে তাই এটি কার্নেলের পৃষ্ঠা-শূন্য করার কাজটির সদৃশ হয় না।
পিটার কর্ডস

6

এটি সম্পর্কে নিশ্চিত নন তবে কেবলমাত্র আমি ব্যাখ্যা করতে পারি যে লিনাক্স হ'ল একটি অনুলিপি অপারেটিং সিস্টেম copy যখন কেউ forkউভয় প্রক্রিয়া একই শারীরিক স্মৃতিতে নির্দেশ করে। একটি প্রক্রিয়া আসলে মেমরিটিতে একবার লিখে মেমরিটি কেবল অনুলিপি করা হয়।

আমি এখানে মনে করি, প্রকৃত শারীরিক স্মৃতি কেবল তখনই বরাদ্দ করা হয় যখন কেউ এটিতে কিছু লেখার চেষ্টা করে। কল করা sbrkবা mmapকেবলমাত্র কার্নেলের মেমরি বুক-কিপ আপডেট করতে পারে। প্রকৃত র‌্যাম তখনই বরাদ্দ করা যেতে পারে যখন আমরা আসলে মেমরিটি অ্যাক্সেস করার চেষ্টা করি।


forkএর সাথে কিছু করার নেই। আপনি যদি এই প্রোগ্রামটির সাথে লিনাক্স বুট করেন তবে আপনি একই আচরণ দেখতে পাবেন /sbin/init। (অর্থাত্ পিআইডি 1, প্রথম ব্যবহারকারী-মোড প্রক্রিয়া)। অনুলিপি-সহ আপনার সঠিক ধারণা ছিল, যদিও: আপনি এগুলিকে নষ্ট না করা পর্যন্ত সদ্য বরাদ্দকৃত পৃষ্ঠাগুলি সমস্ত অনুলিপি একই শূন্য পৃষ্ঠায় ম্যাপ করা আছে pped
পিটার কর্ডেস

কাঁটাচামচ সম্পর্কে জানার ফলে আমাকে অনুমান করতে দেওয়া হয়েছিল।
ডোরন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.