2013-06-06 5 views
28

भीतर मैं एक स्मृति खंड जो MAP_ANONYMOUS साथ mmap के माध्यम से प्राप्त किया गया था।का आवंटन प्रति एक प्रक्रिया

मैं एक ही आकार के पहले एक का संदर्भ जिसमें से एक दूसरे स्मृति खंड कैसे आवंटित और दोनों कॉपी-ऑन लिखने लिनक्स में (कार्य लिनक्स 2.6.36 पल में) कर सकते हैं?

मैं सिर्फ एक नई प्रक्रिया बनाने के बिना fork के रूप में एक ही प्रभाव वास्तव में करने के लिए, चाहते हैं। मैं चाहता हूं कि नया मानचित्रण एक ही प्रक्रिया में रहे।

पूरी प्रक्रिया को मूल और प्रतिलिपि पृष्ठों दोनों पर दोहराने योग्य होना चाहिए (जैसे कि माता-पिता और बच्चा fork जारी रहेगा)।

कारण मैं पूरे सेगमेंट की सीधी प्रति आवंटित नहीं करना चाहता हूं क्योंकि वे कई गीगाबाइट बड़े हैं और मैं स्मृति का उपयोग नहीं करना चाहता जो कॉपी-ऑन-राइट साझा किया जा सकता है।

मैं क्या कोशिश की है:

mmap खंड, साझा गुमनाम। डुप्लिकेशंस mprotect पर इसे केवल पढ़ने के लिए और remap_file_pages के साथ एक दूसरा मैपिंग भी केवल पढ़ने के लिए।

फिर लिखने के प्रयासों को रोकने के लिए libsigsegv का उपयोग करें, मैन्युअल रूप से पृष्ठ की एक प्रति बनाएं और फिर mprotect दोनों को पढ़ने-लिखने के लिए करें।

चाल है, लेकिन बहुत गंदा है। मैं अनिवार्य रूप से अपने स्वयं के वीएम को लागू कर रहा हूं।

दुख की बात mmap आईएन /proc/self/mem वर्तमान लिनक्स पर समर्थित नहीं है, अन्यथा MAP_PRIVATE मानचित्रण चाल चल सकता है।

कॉपी-ऑन-राइट यांत्रिकी लिनक्स वी एम का हिस्सा हैं, वहाँ एक नई प्रक्रिया बनाने के बिना उनमें से उपयोग करने के लिए एक तरह से किया जाना है।

एक नोट के रूप में: मैं मच वी एम में उचित यांत्रिकी मिल गया है।

निम्नलिखित कोड मेरे ओएस एक्स 10.7.5 पर संकलित करता है तथा अपेक्षित व्यवहार है: Darwin 11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64 i386

gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)

#include <sys/mman.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <unistd.h> 
#ifdef __MACH__ 
#include <mach/mach.h> 
#endif 


int main() { 

    mach_port_t this_task = mach_task_self(); 

    struct { 
     size_t rss; 
     size_t vms; 
     void * a1; 
     void * a2; 
     char p1; 
     char p2; 
     } results[3]; 

    size_t length = sysconf(_SC_PAGE_SIZE); 
    vm_address_t first_address; 
    kern_return_t result = vm_allocate(this_task, &first_address, length, VM_FLAGS_ANYWHERE); 

    if (result != ERR_SUCCESS) { 
     fprintf(stderr, "Error allocating initial 0x%zu memory.\n", length); 
      return -1; 
    } 

    char * first_address_p = first_address; 
    char * mirror_address_p; 
    *first_address_p = 'a'; 

    struct task_basic_info t_info; 
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; 

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 
    results[0].rss = t_info.resident_size; 
    results[0].vms = t_info.virtual_size; 
    results[0].a1 = first_address_p; 
    results[0].p1 = *first_address_p; 

    vm_address_t mirrorAddress; 
    vm_prot_t cur_prot, max_prot; 
    result = vm_remap(this_task, 
         &mirrorAddress, // mirror target 
         length, // size of mirror 
         0,     // auto alignment 
         1,     // remap anywhere 
         this_task, // same task 
         first_address,  // mirror source 
         1,     // Copy 
         &cur_prot,   // unused protection struct 
         &max_prot,   // unused protection struct 
         VM_INHERIT_COPY); 

    if (result != ERR_SUCCESS) { 
     perror("vm_remap"); 
     fprintf(stderr, "Error remapping pages.\n"); 
       return -1; 
    } 

    mirror_address_p = mirrorAddress; 

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 
    results[1].rss = t_info.resident_size; 
    results[1].vms = t_info.virtual_size; 
    results[1].a1 = first_address_p; 
    results[1].p1 = *first_address_p; 
    results[1].a2 = mirror_address_p; 
    results[1].p2 = *mirror_address_p; 

    *mirror_address_p = 'b'; 

    task_info(this_task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 
    results[2].rss = t_info.resident_size; 
    results[2].vms = t_info.virtual_size; 
    results[2].a1 = first_address_p; 
    results[2].p1 = *first_address_p; 
    results[2].a2 = mirror_address_p; 
    results[2].p2 = *mirror_address_p; 

    printf("Allocated one page of memory and wrote to it.\n"); 
    printf("*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[0].a1, results[0].p1, results[0].rss, results[0].vms); 
    printf("Cloned that page copy-on-write.\n"); 
    printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[1].a1, results[1].p1,results[1].a2, results[1].p2, results[1].rss, results[1].vms); 
    printf("Wrote to the new cloned page.\n"); 
    printf("*%p = '%c'\n*%p = '%c'\nRSS: %zu\tVMS: %zu\n",results[2].a1, results[2].p1,results[2].a2, results[2].p2, results[2].rss, results[2].vms); 

    return 0; 
} 

मैं लिनक्स में एक ही प्रभाव चाहते हैं।

+0

आप btrfs का उपयोग कर सकते हैं और कॉपी-ऑन-राइट फीचर के साथ अपनी फ़ाइल डुप्लिकेशन का उपयोग कर सकते हैं ... हालांकि, आपके पास एफएस में आपके डेटा की अनावश्यक प्रतियां होंगी। काम करना चाहिए, लेकिन बिल्कुल उच्च प्रदर्शन नहीं। – thejh

+3

कर्नेल को प्रश्न से बाहर खींच रहा है? – thejh

+0

@thejh दुर्भाग्य से यह :(है। कोड पर। एक और फाइल सिस्टम नियोजित करना एक विकल्प या तो एक ही कारण के लिए और performance.'/dev/shm' (नहीं है मशीनों पर परिनियोजन योग्य मैं जड़ नहीं है होना है tmpfs) वास्तव में किस प्रकार की जाती है प्रति-ऑन-राइट ग्राहक कोड, जब दोनों प्रतियों की ग्राहकों को एक ही पता स्थान साझा करने के लिए देखने के लिए चाहिए के रूप में मैं फ़ाइल समर्थित स्मृति के साथ जाने के लिए तैयार हूँ के रूप में दूर है। –

उत्तर

1

हम्म ... आप /dev/shm में MAP_SHARED के साथ एक फ़ाइल बना सकते हैं, इसे लिखें, फिर इसे MAP_PRIVATE के साथ दो बार दोबारा खोलें।

+0

आपका मतलब है कि इसे 'MAP_PRIVATE' से दोबारा खोलें। हाँ, यह काम करता है। एक बार। मुझे प्रक्रिया को दोहराने और पृष्ठों को दोबारा दोहराने में सक्षम होना चाहिए। –

+0

उस मामले में त्रुटि कोड/संदेश क्या होगा? मेरे अनुभव से आप 'MAP_PRIVATE' के साथ जितनी बार चाहें फ़ाइल को' mmap' कर सकते हैं। –

+1

@ डेविड फोस्टर: उसका मतलब है कि उदा। ए नामक बी की कॉपी-ऑन-राइट कॉपी बनाने के बाद और बी में कुछ बदलाव करने के बाद, वह बी की कॉपी-ऑन-राइट कॉपी बनाना चाहता है। इस विधि के साथ यह संभव नहीं है। – thejh

6

मैं (वास्तव में, इसके छबीला सरल रूप में मैं केवल एक जीवित क्षेत्र की फोटो लेने की जरूरत है, मैं प्रतियां की प्रतियां लेने की जरूरत नहीं है) एक ही बात को प्राप्त करने की कोशिश की। मुझे इसके लिए अच्छा समाधान नहीं मिला।

डायरेक्ट कर्नेल समर्थन (या इसकी कमी): मॉड्यूल को संशोधित/जोड़कर इसे प्राप्त करना संभव होना चाहिए। हालांकि मौजूदा कोड से नया गाय क्षेत्र स्थापित करने का कोई आसान तरीका नहीं है।फोर्क द्वारा उपयोग किया गया कोड (copy_page_rank) एक प्रक्रिया/वर्चुअल एड्रेस स्पेस से दूसरे (नया एक) से vm_area_struct कॉपी करें लेकिन मान लें कि नए मैपिंग का पता पुराने के पते जैसा ही है। यदि कोई "रीमेप" सुविधा लागू करना चाहता है, तो पते अनुवाद के साथ vm_area_struct की प्रतिलिपि बनाने के लिए फ़ंक्शन को संशोधित/डुप्लिकेट किया जाना चाहिए।

बीटीआरएफएस: मैंने इसके लिए बीआरटीएफ पर गाय का उपयोग करने का विचार किया। मैंने एक सरल प्रोग्राम मैपिंग दो रिफ्लिंक-एडी फाइलों को लिखा और उन्हें मैप करने की कोशिश की। हालांकि, /proc/self/pagemap के साथ पृष्ठ की जानकारी को देखते हुए फ़ाइल के दो उदाहरण समान कैश पृष्ठों को साझा नहीं करते हैं। (कम से कम जब तक मेरा परीक्षण गलत नहीं है)। तो आप ऐसा करके ज्यादा हासिल नहीं करेंगे। एक ही डेटा के भौतिक पृष्ठ अलग-अलग उदाहरणों के बीच साझा नहीं किए जाएंगे।

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <assert.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/mman.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <inttypes.h> 
#include <stdio.h> 

void* map_file(const char* file) { 
    struct stat file_stat; 
    int fd = open(file, O_RDWR); 
    assert(fd>=0); 
    int temp = fstat(fd, &file_stat); 
    assert(temp==0); 
    void* res = mmap(NULL, file_stat.st_size, PROT_READ, MAP_SHARED, fd, 0); 
    assert(res!=MAP_FAILED); 
    close(fd); 
    return res; 
} 

static int pagemap_fd = -1; 

uint64_t pagemap_info(void* p) { 
    if(pagemap_fd<0) { 
    pagemap_fd = open("/proc/self/pagemap", O_RDONLY); 
    if(pagemap_fd<0) { 
     perror("open pagemap"); 
     exit(1); 
    } 
    } 
    size_t page = ((uintptr_t) p)/getpagesize(); 
    int temp = lseek(pagemap_fd, page*sizeof(uint64_t), SEEK_SET); 
    if(temp==(off_t) -1) { 
    perror("lseek"); 
    exit(1); 
    } 
    uint64_t value; 
    temp = read(pagemap_fd, (char*)&value, sizeof(uint64_t)); 
    if(temp<0) { 
    perror("lseek"); 
    exit(1); 
    } 
    if(temp!=sizeof(uint64_t)) { 
    exit(1); 
    } 
    return value; 
} 

int main(int argc, char** argv) { 

    char* a = (char*) map_file(argv[1]); 
    char* b = (char*) map_file(argv[2]); 

    int fd = open("/proc/self/pagemap", O_RDONLY); 
    assert(fd>=0); 

    int x = a[0]; 
    uint64_t info1 = pagemap_info(a); 

    int y = b[0]; 
    uint64_t info2 = pagemap_info(b); 

    fprintf(stderr, "%" PRIx64 " %" PRIx64 "\n", info1, info2); 

    assert(info1==info2); 

    return 0; 
} 

mprotect + mmap गुमनाम पृष्ठों: यह आपके मामले में काम नहीं करता है, लेकिन एक समाधान मेरी मुख्य स्मृति क्षेत्र के लिए एक MAP_SHARED फ़ाइल का उपयोग करने के लिए है। स्नैपशॉट पर, फ़ाइल कहीं और मैप की जाती है और दोनों उदाहरण असुरक्षित होते हैं। एक लिखने पर, स्नैपशॉट में मैप किए गए एक अज्ञात पृष्ठ, डेटा को इस नए पृष्ठ में कॉपी किया गया है और मूल पृष्ठ असुरक्षित है। हालांकि यह समाधान आपके मामले में काम नहीं करता है क्योंकि आप स्नैपशॉट में प्रक्रिया को दोहराने में सक्षम नहीं होंगे (क्योंकि यह एक सादा MAP_SHARED क्षेत्र नहीं है लेकिन कुछ MAP_ANONYMOUS पृष्ठों के साथ एक MAP_SHARED है। इसके अलावा यह प्रतियों की संख्या के साथ स्केल नहीं करता है: अगर मेरे पास कई गाय प्रतियां हैं, तो मुझे प्रत्येक प्रतिलिपि के लिए एक ही प्रक्रिया दोहराना होगा और इस पृष्ठ को प्रतियों के लिए डुप्लीकेट नहीं किया जाएगा। और मैं मूल क्षेत्र में अज्ञात पृष्ठ को मानचित्र नहीं कर सकता क्योंकि मानचित्र को मानचित्र बनाना संभव नहीं होगा । प्रतियों में गुमनाम पृष्ठों यह समाधान किसी भी तरह काम नहीं करता है

mprotect + remap_file_pages:।। यही एक रास्ता है लिनक्स कर्नेल को छुए बिना ऐसा करते हैं की तरह लग रहा नकारात्मक पक्ष यह है कि सामान्य रूप में, आप करेंगे प्रतिलिपि करते समय शायद प्रत्येक पृष्ठ के लिए remap_file_page syscall बनाना होगा: यह बहुत सी सिस्को बनाने के लिए सक्षम नहीं हो सकता है। किसी साझा पृष्ठ को समर्पित करते समय, आपको कम से कम: remap_file_page नए लिखित-टू-पेज के लिए एक नया/मुफ्त पृष्ठ चाहिए, नए पृष्ठ को एम-अन-सुरक्षित करें। प्रत्येक पृष्ठ की गणना करने के लिए जरूरी है।

मुझे नहीं लगता कि mprotect() आधारित दृष्टिकोण बहुत अच्छी तरह से स्केल करेंगे (यदि आप इस तरह की बहुत सारी मेमोरी संभालते हैं)। लिनक्स पर, mprotect() मेमोरी पेज ग्रैन्युलरिटी पर काम नहीं करता है लेकिन vm_area_struct ग्रैन्युलरिटी (प्रविष्टियां जो आपको/prod // maps में मिलती हैं) पर काम नहीं करती हैं। स्मृति पेज का पठन स्तर पर एक mprotect() कर गिरी लगातार विभाजित करने के लिए कारण और vm_area_struct मर्ज हो जाएगा:

  • आप एक बहुत mm_struct साथ खत्म हो जाएगा;

  • एक vm_area_struct (जिसे वर्चुअल मेमोरी से संबंधित संचालन के लॉग के लिए उपयोग किया जाता है) को देखकर O(log #vm_area_struct) पर है लेकिन इसका अभी भी नकारात्मक प्रदर्शन प्रभाव हो सकता है;

  • उन संरचनाओं के लिए स्मृति खपत।

कारण इस तरह की के लिए, remap_file_pages() syscall आदेश में एक फ़ाइल के गैर रेखीय स्मृति मानचित्रण करने के लिए बनाया गया था [http://lwn.net/Articles/24468/]। एमएमएपी के साथ ऐसा करने के लिए, vm_area_struct का लॉग की आवश्यकता है। मुझे घटना नहीं लगता है कि वे पेज ग्रैन्युलरिटी मैपिंग के लिए डिज़ाइन किए गए थे: remap_file_pages() इस उपयोग के मामले के लिए बहुत अनुकूलित नहीं है क्योंकि इसे प्रति पृष्ठ एक syscall की आवश्यकता होगी।

मुझे लगता है कि कर्नेल को ऐसा करने का एकमात्र व्यवहार्य समाधान है। Remap_file_pages के साथ उपयोगकर्ता स्पेस में ऐसा करना संभव है लेकिन यह शायद काफी अक्षम होगा क्योंकि स्नैपशॉट उत्पन्न करने के लिए पृष्ठों की संख्या में आनुपातिक कई सिस्कोल की आवश्यकता होगी। Remap_file_pages का एक संस्करण चाल कर सकता है।

हालांकि यह दृष्टिकोण कर्नेल के पृष्ठ तर्क को डुप्लिकेट करता है। मुझे लगता है कि हमें कर्नेल को ऐसा करने देना चाहिए। सब कुछ, कर्नेल में एक कार्यान्वयन बेहतर समाधान प्रतीत होता है। कर्नेल के इस हिस्से को जानता है जो किसी के लिए, यह करना काफी आसान होना चाहिए।

केएसएम (कर्नेल समान पृष्ठ विलय): कर्नेल कर सकता है एक बात है। यह पृष्ठों को deduplicate करने का प्रयास कर सकते हैं। आपको अभी भी डेटा कॉपी करना होगा, लेकिन कर्नेल उन्हें मर्ज करने में सक्षम होना चाहिए। आपको अपनी प्रतिलिपि के लिए एक नया अनाम क्षेत्र mmap करने की आवश्यकता है, इसे मैन्युअल रूप से memcpy और madvide(start, end, MADV_MERGEABLE) क्षेत्रों के साथ कॉपी करें। आप KSM सक्षम करने के लिए (रूट में) की जरूरत है:

echo 1 > /sys/kernel/mm/ksm/run 
echo 10000 > /sys/kernel/mm/ksm/pages_to_scan 

यह काम करता है, यह मेरी कार्यभार के साथ इतनी अच्छी तरह से काम नहीं करता है, लेकिन यह शायद इसलिए है क्योंकि पृष्ठों अंत में एक बहुत साझा नहीं किया जाता है। नकारात्मकता यह है कि आपको अभी भी प्रतिलिपि करना है (आपके पास एक कुशल गाय नहीं हो सकता है) और फिर कर्नेल पृष्ठ को अन-मर्ज करेगा। प्रतिलिपि करते समय यह पेज और कैश दोष उत्पन्न करेगा, केएसएम डिमन थ्रेड बहुत सारे सीपीयू का उपभोग करेगा (मेरे पास सीपीयू पूरे सिमुलेशन के लिए ए 00% पर चल रहा है) और शायद एक कैश लॉग का उपभोग करेगा। इसलिए आपको प्रतिलिपि करते समय समय नहीं मिलेगा लेकिन आपको कुछ स्मृति मिल सकती है। यदि आपका मुख्य प्रेरणा लंबे समय तक कम स्मृति का उपयोग करना है और आपको प्रतियों से परहेज करने के बारे में बहुत कुछ परवाह नहीं है, तो यह समाधान आपके लिए काम कर सकता है।

+0

आपके पास बहुत अच्छे विचार हैं, दुख की बात है कि मेरे उद्देश्य के लिए कोई भी पर्याप्त आवश्यकताओं को पूरा नहीं करता है। मैंने अपने प्रश्न में पहले से ही ** mprotect + mmap अज्ञात पेज ** और ** mprotect + remap_file_pages ** पर चर्चा की है। मैंने बीआरटीएफएस में नहीं देखा है, इसलिए इसे देख सकते हैं।केएसएम दुख की बात नहीं है क्योंकि यह मुझे पहले स्थान पर प्रतियां बनाने पर निर्भर करता है और मैं उनको बनाने से बचना चाहता हूं। मैंने खुद को लिनक्स कर्नेल को पैच करने पर भी देखा है, लेकिन इसे करने का समय कभी नहीं मिला। कुछ अच्छे विचारों के लिए +1। –

+1

संदर्भ के लिए, 'remap_file_pages' अब [बहिष्कृत] है (http://man7.org/linux/man-pages/man2/remap_file_pages.2.html) और शायद धीमे अनुकरण द्वारा हटा दिया जाएगा/प्रतिस्थापित किया जाएगा। – ysdx

+0

कर्नेल के साथ गड़बड़ करने पर गंभीरता से किसी के लिए कॉफी की अनुशंसित मात्रा क्या है। मैं एक दोस्त से पूछ रहा हूं ... – BlamKiwi

संबंधित मुद्दे