Skip to main content

Hiểu về tổ chức bộ nhớ của Linux thông qua ví dụ về Memory Mapping

Cơ sở lý thuyết về bộ nhớ ảo, bộ nhớ logic và bộ nhớ vật lý

Hoạt động ánh xạ địa chỉ ảo tới địa chỉ vật lý

Linux cung cấp cho các tiến trình hệ thống quản lý bộ nhớ ảo, nơi mỗi địa chỉ nhớ ảo có khả năng được ánh xạ tới một địa chỉ vật lý. Với độ dài 32 bit, toàn bộ không gian địa chỉ mỗi tiến trình có khả năng truy nhập là 2^32 ~ 4 Gigabit. Linux chia không gian địa chỉ này thành các trang nhớ (page) có độ dài bằng nhau (4096 bytes), mỗi khi tiến trình yêu cầu một vùng nhớ, cả trang nhớ tương ứng (chứa vùng nhớ) sẽ được cấp cho tiến trình. Bộ nhớ vật lý hệ thống chính là lượng RAM có trong hệ thống, Linux cũng chia bộ nhớ vật lý này thành các trang bằng nhau, gọi là các page frame, mỗi page frame được đánh số thứ tự gọi là các page frame number.
Các địa chỉ ảo có thể sẽ được ánh xạ thành địa chỉ vật lý dựa vào các phần cứng gọi là các MMU (Memory Management Unit) theo một phương pháp gọi là lazy allocation. Theo phương pháp này, mỗi khi một vùng nhớ ảo được cấp phát cho tiến trình, nhân hệ điều hành sẽ chưa ánh xạ nó tới địa chỉ vật lý, vùng này được gọi là vùng unmapped, khi vùng nhớ thực sự được sử dụng để lưu trữ dữ liệu, MMU của hệ thống phát hiện ra vùng nhớ ảo chưa mapped tới một vùng nhớ vật lý nào, MMU sẽ tạo ra một sự kiện gọi là page fault, và raise một ngắt tới CPU, nhân Linux sẽ xử lý sự kiện này và ra lệnh cho khối TLB (Translation Lookaside Buffer) bên trong MMU tiến hành ánh xạ trang nhớ ảo đó với một địa chỉ vật lý, sau đó quyền điều khiển sẽ được trả lại cho tiến trình và quá trình trên sẽ là trong suốt đối với tiến trình. Để hiểu rõ phương pháp này chúng ta xét đoạn mã sau:
char *buffer = malloc(10000);
char *result;
if(0 == func1())
    result = func2();
else
    result = func3();
memcpy(buffer, result, 10000);
func4(buffer);
free(buffer)
Tại thời điểm đầu tiên, khi tiến trình xin cấp phát 10000 bytes bộ nhớ tương đương với 10000/4096 ~ 3 trang nhớ ảo, hệ điều hành sẽ cấp phát cho tiến trình 3 trang nhớ ảo, 3 trang nhớ này có địa chỉ đầu lưu ở buffer chưa thực sự được ánh xạ tới một vùng nhớ vật lý nào, sau đó tiến trình tiếp tục xử lý nhiệm vụ của nó tới khi nó gọi hàm memcpy, tại đây nó cần ghi dữ liệu tới vùng nhớ nó đã xin cấp phát, khi đó MMU của hệ thống sẽ phát hiện ra vùng nhớ buffer chưa có một địa chỉ vật lý, nó sẽ tạo ra một sự kiện page fault tới CPU, nhân Linux xử lý sự kiện sẽ dừng tiến trình, lưu context của tiến trình lại và xử lý ngắt, hàm phục vụ ngắt tìm kiếm ba page frame (vật lý) chưa được sử dụng và tạo ra ánh xạ giữa ba trang nhớ của tiến trình với ba page frame
Sau khi ba trang nhớ ảo đã được ánh xạ tới địa chỉ vật lý, nhân hệ điều hành sẽ khôi phục lại tiến trình, tiến trình tiếp tục copy dữ liệu vào vùng nhớ ảo và thực hiện tác vụ của mình, tiến trình hoàn toàn không biết những gì đã xảy ra ở nhân
Chúng ta thấy rằng phương pháp lazy allocation sẽ cải thiện hiệu năng cấp phát bộ nhớ, khi mà các tiến trình yêu cầu cấp phát, nó sẽ được trả về các địa chỉ ảo rất nhanh mà chưa quan tâm tới các địa chỉ vật lý, phương pháp này đồng thời cũng sẽ cải thiện hiệu năng sử dụng bộ nhớ, khi các vùng nhớ được cấp phát nhưng không được sử dụng sẽ không bao giờ được ánh xạ tới bộ nhớ vật lý. Đồng thời chúng ta cũng thấy các địa chỉ ảo liên tục không duy trì một địa chỉ vật lý liên tục, việc lựa chọn page frame nào phù hợp để ánh xạ tới bộ nhớ ảo hoàn toàn phụ thuộc vào trạng thái hệ thống.

Bộ nhớ ảo của nhân và tiến trình

Trong mã nguồn của nhân, nhân cũng sử dụng địa chỉ ảo. Tuy nhiên các vùng địa chỉ được phân tách hoàn toàn với tiến trình và tiến trình không thể truy xuất trực tiếp tới các vùng nhớ thuộc về nhân. Ví dụ trong hệ thống 32bit, có 4G RAM, nhân hệ điều hành sẽ sử dụng 1G địa chỉ cao và phần còn lại thuộc về tiến trình.
Vùng nhớ ảo thuộc về nhân, nhân hệ điều hành chia làm hai loại bộ nhớ, bộ nhớ ảo (kernel virtual address) và bộ nhớ logic (kernel logical address).Bộ nhớ logic thực chất là bộ nhớ ảo với một vài khác biệt. Khác biệt lớn nhất là bộ nhớ logic sẽ có địa chỉ vật lý liên tục trong khi bộ nhớ ảo thì không.
Vì có một địa chỉ vật lý liên tục cho nên địa chỉ logic có một số tính chất mà bộ nhớ ảo không có, như offset giữa bộ nhớ logic và bộ nhớ vật lý luôn là một hằng số, do đó việc chuyển đổi giữa bộ nhớ này trở nên dễ dàng. Ví dụ địa chỉ logic là 0xc00000000 ứng với địa chỉ vật lý là 0x00000000 thì 0xc00000001 sẽ ứng với 0x00000001 …
Địa chỉ logic được thiết kế để phù hợp với các hoạt động DMA, khi mà các hoạt động này sẽ sử dụng đến địa chỉ vật lý, giả sử DMA sẽ copy 10000 byte từ ngoại vi vào bộ nhớ tại địa chỉ của page frame number 2, như thế sẽ cần tới 3 page frame và pfn#2, pfn#3, pfn#4  sẽ có dữ liệu sau khi DMA kết thúc, để đọc dữ liệu này CPU sẽ truy vấn tới địa chỉ logic đã được chuyển từ địa chỉ vật lý của pfn#2, như thế dữ liệu sẽ nằm liên tục trong địa chỉ logic và được đọc dễ dàng. Điều này dường như là không khả thi đối với địa chỉ ảo, khi mà các địa chỉ ảo không được ánh xạ liên tục tới địa chỉ vật lý.
Nhân hệ điều hành cung cấp cho chúng ta hàm kmalloc vmalloc để cấp phát bộ nhớ trong vùng nhớ logical và virtual tương ứng.

Không gian địa chỉ người dùng

Đối với vùng nhớ thuộc về không gian người dùng (user virtual memory), vùng nhớ này được cấp phát tới tiến trình, và tất cả đều là địa chỉ ảo và được ánh xạ không liên tục tới địa chỉ vật lý. Khi một tiến trình được bắt đầu, bộ nhớ của nó được tổ chức như sau
Vùng text segment chứa mã thực thi của tiến trình, vùng data segment chứa biến tĩnh (toàn cục) được khởi tạo, vùng bss segment chứa biến tĩnh (toàn cục) không được khởi tạo, vùng stack chứa tham số cho hàm và biến cục bộ, vùng heap dùng để cấp phát bộ nhớ động, các khoảng trắng giữa các vùng của tiến trình và biên giới bộ nhớ ảo được tạo do lý do security.
Khoảng trắng nằm giữa vùng stack và heap được gọi là vùng Unmapped region, nó hoàn toàn đồng nghĩa với vùng Unmapped region trong bài Quản lý bộ nhớ động. Theo lý thuyết của phương pháp lazy allocation vùng này chưa được ánh xạ tới bất kỳ địa chỉ vật lý nào. Vùng này không chỉ được sử dụng cho mục đích mở rộng stack và heap. Nó còn được dùng cho một kỹ thuật đặc biệt, được gọi là Memory Mapping

Kỹ thuật Memory Mapping

Memory Mapping là một phương pháp hết sức đặc sắc và hữu dụng của các hệ thống Unix/Linux hiện đại. Memory mapping cho phép một tiến trình truy nhập một vùng nhớ thuộc về nhân hệ điều hành hoặc truy nhập không gian địa chỉ của ngoại vi(Input/Output) từ đó chiếm quyền điều khiển các thiết bị này từ hệ điều hành. Với phương pháp này, hiệu năng truyền thông giữa không gian nhân (kernel space) và không gian tiến trình (user space/process space) được cải thiện đáng kể nhờ tránh được việc copy dữ liệu không cần thiết giữa các không gian này.

Cấu trúc hàm của mmap và munmap

       void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
       int munmap(void *addr, size_t length);

Các tham số của hàm mmap 

Trường addr là địa chỉ người dùng muốn nơi mapping được bắt đầu, địa chỉ phải nằm trong vùng Unmapped region và trỏ tới bắt đầu của trang nhớ ảo, thông thường người dùng sẽ không biết vùng địa chỉ nào thuộc về Unmapped region hay vị trí nào là đầu trang vì các thông tin này đều do hệ điều hành quản lý, do đó, người dùng sẽ truyền vào NULL để nhân Linux tự quyết định.Tuy nhiên nếu người dùng trước đó đã gọi mmap, nhận về một địa chỉ và đã dùng munmap để unmap nó, bây giờ người sử dụng muốn tái sử dụng địa chỉ đó, thì có thể sử dụng đối addr cho mục đích tái sử dụng    
Trường length mô tả độ dài của mapping, nhân hệ điều hành sẽ tự động roundup trường này tới bội của độ dài một trang (4KB). Ví dụ như length là 5000 byte thì kernel sẽ roundup nó tới 8192 byte =  2 pages
Trường prot mô tả như quyền truy xuất của mapping, các quyền có thể là đọc ghi và thực thi, các quyền này tương ứng với quyền đối với file được mở cho mapping, thông thường khi chạy binary có mmap, chúng ta chạy ở quyền root (cao nhất)
Trường flags mô tả rất nhiều tùy chọn trong đó hai tùy chọn quan trọng nhất là
MAP_SHARED
            Sử dụng trong các ứng dụng chia sẻ thông tin giữa hai tiến trình hoặc chia sẻ thông tin giữa tiến trình và nhân, khi sử dụng cờ này, giả sử có hai tiến trình đều mapping vào một vùng nhớ mỗi khi một bên ghi nội dung vào vùng đó, bên còn lại có thể đọc nội dung đó
MAP_PRIVATE
            Sử dụng để bảo vệ nội dụng của một bên trong trường hợp có hai bên (hai tiến trình hoặc tiến trình và nhân) cùng mapping vào một vùng nhớ (hai tiến trình là cha-con), khi sử dụng cờ này, nếu một bên ghi nội dung, nhân Linux sẽ sử dụng kỹ thuật copy-on-write copy nội dung mới vào một vùng nhớ vật lý khác, như thế bên còn lại sẽ không thể đọc được nội dung mới được ghi
Trường offset mô tả vị trí trong file mà người dùng muốn mapping nội dung lên, trường này được sử dụng trong các ứng dụng của mmap liên quan tới trải nội dung của file lên một vùng nhớ ảo
Hàm mmap sẽ trả về con trỏ trỏ tới địa chỉ được mapped trong trường hợp thành công, nếu lỗi (void *)-1 sẽ được trả về. Đồng thời errno được thiết lập tương ứng để tìm ra nguyên nhân cụ thể gây lỗi

Các tham số của hàm munmap 

Trường addr chính là địa chỉ muốn thực hiện unmap, trường này chính là con trỏ trả ra của mmap trong trường hợp thành công
Trường length mô tả độ dài muốn unmap.
Hàm munmap trả về 0 nếu thành công, trả về -1 nếu lỗi.
Truyền thông giữa userspace và kernel space sử dụng mmap
Với cách thức quản lý bộ nhớ của hệ điều hành Linux như đã trình bày ở trên, chúng ta sẽ tạo ra một ứng dụng cho phép chia sẻ nội dung giữa tiến trình và nhân Linux, nguyên lý căn bản của ứng dụng sẽ là cho phép một tiến trình tạo ra một mapping mà mapping này có cùng địa chỉ vật lý với vùng địa chỉ logical và virtual trong nhân Linux. Mô hình như sau
Vì mapping trong không gian địa chỉ người dùng có cùng địa chỉ vật lý với các vùng nhớ trong không gian địa chỉ trong nhân, do đó nếu nhân Linux đọc/ghi dữ liệu tới các vùng nhớ được sử dụng để mapping của nó, các nội dung này sẽ đều có thể đọc/ghi được ở tiến trình và ngược lại

Thực hiện bằng mã nguồn

Chúng ta cần viết một loadable module để thực hiện việc mapping ở nhân Linux, loadable module sẽ tạo ra một character driver, ứng dụng ở chạy ở không gian địa chỉ người dùng có thể giao tiếp với loadable module thông qua character driver này
Luồng xử lý ở application đơn giản như sau
Ở lớp ứng dụng chỉ đơn giản là mở character driver file, đây là một file đặc biệt được phục vụ bởi các phương thức đăng ký ở loadable module
Loadable module khi khởi tạo sẽ vừa đăng ký các phương thức cho character driver vừa cấp phát vùng nhớ trong cả hai vùng logical và virtual để tiến hành mapping
Character driver file được khai báo như sau 

    static dev_t mmap_dev; 

    static struct cdev mmap_cdev; 

    static int mmap_open(struct inode *inode, struct file *filp); 

    static int mmap_release(struct inode *inode, struct file *filp); 

    static int mmap_mmap(struct file *filp, struct vm_area_struct *vma);      

    static struct file_operations mmap_fops = { 

            .open = mmap_open, 

            .release = mmap_release, 

            .mmap = mmap_mmap, 

            .owner = THIS_MODULE, 
    }; 
    static int mmap_open(struct inode *inode, struct file *filp) 
    { 
            return 0; 
    } 
    static int mmap_release(struct inode *inode, struct file *filp) 
    { 
            return 0; 
    }
Character driver hiện tại có ba phương thức, hai phương thức open và release thực chất không chúng ta cần làm gì vì nhân Linux đã thực hiện mọi thứ cần thiết cho chúng ta, chúng ta chỉ cần implement phương thức thứ ba là mmap.

Các con trỏ global trỏ đến các vùng nhớ logical và virtual cũng được khai báo, số lượng tối đa cho phép tiến hành mapping là 16 trang nhớ

    #define NPAGES 16 

    static int *vmalloc_area;  

    static int *kmalloc_area; 

    static void *kmalloc_ptr;


Khởi tạo loadable module, các vùng nhớ này sẽ được cấp phát và roundup tới đầu trang nhớ ảo, nội dung của chúng cũng được điền trước, chúng ta sẽ đọc thông tin này ở application và có thể chỉnh sửa chúng tùy ý

    static int __init mmap_init_kernel(void) 
    { 
            int ret = 0; 
            int i; 
            unsigned long pfn;
            char *vmalloc_area_ptr = NULL;
            if ((kmalloc_ptr = kmalloc((NPAGES + 2) * PAGE_SIZE, GFP_KERNEL)) == NULL) { 
                    ret = -ENOMEM; 
                    goto out; 
            }
            kmalloc_area = (int *)((((unsigned long)kmalloc_ptr) + PAGE_SIZE - 1) & PAGE_MASK);  
            if ((vmalloc_area = (int *)vmalloc(NPAGES * PAGE_SIZE)) == NULL) { 
                    ret = -ENOMEM; 
                    goto out_kfree; 
            }                       
            if ((ret = alloc_chrdev_region(&mmap_dev, 0, 1, "mmap")) < 0) { 
                    printk(KERN_ERR "could not allocate major number for mmap\n"); 
                    goto out_vfree; 
            } 
            cdev_init(&mmap_cdev, &mmap_fops); 
            if ((ret = cdev_add(&mmap_cdev, mmap_dev, 1)) < 0) { 
                    printk(KERN_ERR "could not allocate chrdev for mmap\n"); 
                    goto out_unalloc_region; 
            } 
            for (i = 0; i < NPAGES * PAGE_SIZE; i+= PAGE_SIZE) { 
                    SetPageReserved(vmalloc_to_page((void *)(((unsigned long)vmalloc_area) + i))); 
                    SetPageReserved(virt_to_page(((unsigned long)kmalloc_area) + i)); 
            } 
            for (i = 0; i < (NPAGES * PAGE_SIZE / sizeof(int)); i += 2) { 
                    vmalloc_area[i] = (0xeeff << 16) + i; 
                    vmalloc_area[i + 1] = (0xffee << 16) + i; 
                    kmalloc_area[i] = (0xaabb << 16) + i; 
                    kmalloc_area[i + 1] = (0xbbaa << 16) + i; 
            } 
            return ret;            
      out_unalloc_region: 
            unregister_chrdev_region(mmap_dev, 1); 
      out_vfree: 
            vfree(vmalloc_area); 
      out_kfree: 
            kfree(kmalloc_ptr); 
      out: 
            return ret; 
    }  
Chúng ta dùng hàm alloc_chrdev_region để tạo ra character driver mmap, các bạn cũng có thể dùng hàm register_chrdev_region. Tuy nhiên để dùng register_chrdev_region các bạn cần phải biết số major number nào trong hệ thống còn trống để tránh confict, major number và minor number là phương thức đánh số cho các character driver để ứng dụng có thể phân biệt lớp các thiết bị trong Linux, alloc_chrdev_region đơn giản là để việc lựa chọn cho nhân Linux, nhân sẽ tự biết số major number nào còn trống và đưa character driver mmap vào đó, sau khi đăng ký xong, chúng ta có thể tìm lại nhân đã đăng ký cho character driver mmap vào major number nào bằng cách cat /proc/devices | grep mmap
Việc đăng ký các phương thức cho driver không tạo ra file thực tế trong thư mục /dev. Chúng ta cần tự tạo ra bằng câu lệnh mknod /dev/mmap c major_number 0 trong đó major number là số lấy được từ /proc/devices, 0 là minor number đã được hardcode trong hàm alloc_chrdev_region. Tất cả các việc này đều phải tiến hành trước khi application chạy,nếu không application sẽ trả về lỗi.

Trở lại với thiết kế của phương thức mmap, tại đây chúng ta cần một chút thông tin về các cấu trúc dữ liệu của nhân và những gì nhân Linux đã làm
Về cơ bản Linux quản lý không gian địa chỉ của tiến trình thông qua một cấu trúc dữ liệu là mm_struct, cấu trúc này được trỏ tới bởi cấu trúc task_struct, cấu trúc quản lý chính tiến trình, mm_struct duy trì các con trỏ tới các vị trí là memory segment của tiến trình




Mm_struct cũng duy trì một danh sách liên kết được gọi là vm_area_struct, cấu trúc này mô tả tất cả địa chỉ các segment trên theo một cấu trúc thống nhất
Mm_struct cũng sử dụng một cây đỏ đen (red-black tree) để quản lý tất cả các vùng unmapped trong Unmapped region (các vùng này có thể không liên tục nhau do hoạt động mmap/munmap từ phía process). Theo đó mỗi khi ở phía người dùng gọi đến mmap để tiến hành mapping, nếu đối addr truyền vào là NULL (yêu cầu nhân tìm một vùng phù hợp) hoặc addr không còn phù hợp (do không đủ độ dài), nhân Linux sẽ tìm kiếm trong cây đỏ đen của mm_struct, sử dụng vm_area_struct để mô tả vùng unmapped đã tìm được trong cây và truyền tới phương thức mmap được khai báo trong file operation
    static struct file_operations mmap_fops = { 
            .open = mmap_open, 
            .release = mmap_release, 
            .mmap = mmap_mmap
            .owner = THIS_MODULE, 
    };  
Cấu trúc vma_area_struct có con trỏ vm_start để lưu địa chỉ đầu vùng, vm_end để lưu địa cuối vùng, trường vm_pgoff để lưu offset người dùng truyền vào hàm mmap
Như vậy chúng ta có thể hình dung luồng xử lý của mmap
Những ô màu vàng là những gì nhân Linux đã thực hiện cho chúng ta. Còn lại ô màu xanh là phương thức mmap_mmap chúng ta cần phải làm trong loadable module
Tại điểm này chúng ta xem xét nếu offset của hàm mmap là 0 thì chúng ta tiến hành map vào vùng virtual còn nếu là NPAGES (16) thì map vào vùng logical
    static int mmap_mmap(struct file *filp, struct vm_area_struct *vma) 
    { 
            if (vma->vm_pgoff == 0) { 
                    return mmap_vmem(filp, vma); 
            } 
            if (vma->vm_pgoff == NPAGES) { 
                    return mmap_kmem(filp, vma); 
            } 
            return -EIO; 
    }  
Nhân Linux cung cấp cho chúng ta hàm remap_pfn_range để thực hiện việc mapping vùng nhớ unmap tới địa chỉ vật lý, xét cấu trúc hàm của remap_pfn_range
int remap_pfn_range (
struct vm_area_struct * vma,

unsigned long addr,

unsigned long pfn,

unsigned long size,

pgprot_t prot);
Trường vma trỏ tới vma_area_struct sử dụng để mô tả vùng unmapped area mà nhân đã tìm được
Trường start mô tả địa chỉ đầu tiên bắt đầu tiến hành mapping, thường trỏ tới vma->vm_start
Trường pfn là page frame number của trang vật lý sẽ sử dụng map tới
Trường size mô tả độ dài sẽ tiến hành mapping
Trường prot mô tả các quyền truy nhập tới vùng được map
Như thế hàm remap_pfn_range map địa chỉ vật lý thông qua page frame number với địa chỉ unmapped bắt đầu từ addr, như vậy chúng ta cần lấy được page frame number của vùng virtual và vùng logical. Với địa chỉ ảo, Linux cung cấp cho chúng ta hàm vmalloc_to_pfn để có thể chuyển đổi dễ dàng, tuy nhiên chúng ta nhớ rằng địa chỉ virtual không duy trì các trang nhớ vật lý một cách liên tục, do đó chúng ta cần phải map từng trang một với vùng unmap
    int mmap_vmem(struct file *filp, struct vm_area_struct *vma) 
    { 
            int ret; 
            long length = vma->vm_end - vma->vm_start; 
            unsigned long start = vma->vm_start; 
            char *vmalloc_area_ptr = (char *)vmalloc_area; 
            unsigned long pfn; 
            if (length > NPAGES * PAGE_SIZE) 
                    return -EIO; 
            while (length > 0) { 
                    pfn = vmalloc_to_pfn(vmalloc_area_ptr); 
                    if ((ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE, 
                                               PAGE_SHARED)) < 0) { 
                            return ret; 
                    } 
                    start += PAGE_SIZE; 
                    vmalloc_area_ptr += PAGE_SIZE; 
                    length -= PAGE_SIZE; 
            } 
            return 0; 
    }  
Với địa chỉ logical chúng ta có công thức quan hệ giữa địa chỉ vật lý và pfn

physical_address = PFN * page_size + offset
Trong đó page_size là 4096, và bởi vì là đầu trang thì offset là 0, do đó ta có thể tính ngược lại PFN thông qua địa chỉ vật lý, Linux cung cấp cho chúng ta hàm chuyển đồi từ địa chỉ logic về địa chỉ vật lý, chúng ta cần dịch địa chỉ này đi 12 bit (chia cho 4096) là sẽ có được pfn.
    int mmap_kmem(struct file *filp, struct vm_area_struct *vma) 
    { 
            int ret, i; 
            long length = vma->vm_end - vma->vm_start; 
            unsigned long pfn;
            if (length > NPAGES * PAGE_SIZE) 
                    return -EIO;
            if ((ret = remap_pfn_range(vma, 
                                       vma->vm_start, 
                                       virt_to_phys((void *)kmalloc_area) >> PAGE_SHIFT, 
                                       length, 
                                       vma->vm_page_prot)) < 0) { 
                    return ret; 
            } 
             
            return 0; 
    }  
Nhớ rằng địa chỉ logical sẽ có các trang nhớ vật lý liên tục, do đó chúng ta có thể map tất cả các trang chỉ trong một lần gọi
Mã nguồn chương trình ở userpace như sau
    #include <stdio.h> 
    #include <unistd.h> 
    #include <sys/mman.h> 
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <fcntl.h> 
    #include <stdlib.h> 
     
    #define NPAGES 16 
     
    /* this is a test program that opens the mmap driver.
       It reads out values of the kmalloc() and vmalloc()
       allocated areas and checks for correctness.
       You need a device special file to access the driver.
       The device special file is called 'node' and searched
       in the current directory.
       To create it
       - load the driver
         'insmod mmap_kernel.ko'
       - find the major number assigned to the driver
         'grep mmap /proc/devices'
       - and create the special file (assuming major number 247)
         'mknod /dev/mmap c 247 0'
    */ 
     
    int main(void) 
    { 
      int fd; 
      unsigned int *vadr, *vadr1; 
      unsigned int *kadr, *kadr1; 

      void *mmap_addr = malloc(NPAGES*4096); 
     
      int len = NPAGES * getpagesize(); 
     
      if ((fd=open("/dev/mmap", O_RDWR|O_SYNC))<0) 
      { 
          perror("open"); 
          exit(-1); 
      } 
     
      vadr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); 
       
      if (vadr == MAP_FAILED) 
      { 
              perror("mmap"); 
              exit(-1); 
      } 
      printf("vadr at %p mmap_addr %p\r\n", vadr, mmap_addr); 

      printf("0x%x 0x%x\n", vadr[0], vadr[1]); 
       
      kadr = mmap(0, len, PROT_READ|PROT_WRITE, MAP_SHARED| MAP_LOCKED, fd, len); 
       
      if (kadr == MAP_FAILED) 
      { 
              perror("mmap"); 
              exit(-1); 
      } 
      printf("kadr at %p\r\n", kadr);

      printf("0x%x 0x%x\n", kadr[0], kadr[1]); 

      munmap(vadr, len);
      munmap(kadr, len);
     
      while(1)
      {
          sleep(1);
      }
 
      close(fd); 
              free(mmap_addr);
      return(0); 
    }
Cuối cùng không gian địa chỉ process sẽ có thêm vùng mmap nằm giữa stack và heap

Các thao tác cơ bản

Biên dịch loadable module. Vào thư mục mã nguồn, ở quyền root gõ
make
insmod mmap_kernel.ko
cat /proc/devices | grep mmap /*Đi tìm major number*/
mknod /dev/mmap c 247 0 /*Giả sử tìm được là 247*/
gcc mmap_app.c –o mmap_app
./mmap_app /*Giả sử process id 1234*/
cat /proc/1234/maps,
Chúng ta sẽ nhìn thấy hai vùng nhớ được map tới mmap_app thông qua /dev/mmap, hai vùng này trả về từ remap_pfn_range, chính là hai địa chỉ kadr và vadr và mmap_app có thể đọc ghi nó dễ dàng

Ứng dụng của mmap

Mmap có rất nhiều ứng dụng khác nhau, đặc biệt là các ứng dụng yêu cầu hiệu năng cao, các ứng dụng có thể yêu cầu gửi lượng lớn dữ liệu từ kernel tới process hoặc từ process tới process như xử lý ảnh hoặc xử lý bản tin mạng máy tính, mmap tạo ra một quá trình truyền dữ liệu không cần copy giữa các vùng nhớ
Một ứng dụng nữa của mmap là đưa không gian địa chỉ ngoại vi tới process, tại đó process có thể truy vấn thông tin và điều khiển các ngoại vi này.Tôi sẽ có một bài về vấn đề này

Toàn bộ mã nguồn của framework các bạn có thê download tại GitHub của Nhã Uyên Education
https://github.com/nhauyeneducation/linux_systemtraining

Tin học Nhã Uyên là một tổ chức thành lập với mục tiêu đào tạo kỹ sư lập trình hệ thống, kỹ sư lập trình nhúng và kỹ sư lập trình mạng trên nền tảng hệ điều hành Linux/Unix tại Việt Nam. 
Mời các bạn vào Facebook của Tin học Nhã Uyên tại

https://www.facebook.com/tinhocnhauyen/ để theo dõi các bài viết kỹ thuật chất lượng tiếp theo của Tin học Nhã Uyên. Xin trân trọng cảm ơn các bạn

Comments

Popular posts from this blog

Về một phương pháp quản lý bộ nhớ động trong Linux

Các kiến thức cơ bản về hệ thống Thư viện glibc trong Linux cung cấp cho chúng ta bốn hàm cơ bản để quản lý bộ nhớ động, ba trong số đó là các hàm cấp phát bộ nhớ, hàm còn lại là giải phóng bộ nhớ Hàm void *malloc(size_t size) nhận đầu vào số byte cần cấp phát và trả lại vùng nhớ được cấp phát. Nếu không tìm thấy vùng nhớ thỏa mãn độ dài cần cấp phát malloc sẽ trả về NULL Hàm void *calloc(size_t nmemb, size_t size ) sẽ cấp phát bộ nhớ cho một mảng nmemb*size và trả về con trỏ tới vùng nhớ được cấp phát, nhớ rằng mặc dù cấp phát bộ nhớ cho một mảng các phần tử nhưng nó vẫn là một vùng nhớ liên tục trong heap, vùng nhớ này được ghi giá trị 0 trên toàn vùng trước khi trả về Hàm void *realloc(void *ptr, size_t size) sẽ thay đổi số byte được cấp phát ở ptr và trả lại một vùng nhớ được cấp phát có độ dài size và có nội dung như là vùng nhớ ở ptr. Vùng nhớ được trả lại bởi realloc có thể chính là ptr trong trường hợp các vùng xung quanh ptr có thể đủ khả năng cung cấp một vùng dài h...

Về phương pháp notificall chain và các ứng dụng

Lời mở đầu Nhân hệ điều hành Linux được cấu thành từ rất nhiều các sub-system, mỗi sub-system này thực hiện các chức năng riêng biệt nhau, tuy nhiên một số nhóm sub-system có thể mong muốn nhận tập các sự kiện giống nhau. Nhân Linux thiết kế một phương pháp truyền thông để có thể chia sẻ một sự kiện tới nhiều các sub-system vừa tạo ra sự linh hoạt trong code vừa có khả năng mở rộng dễ dàng trong tương lai. Phương pháp đó gọi là notifier-call chain. Việc hiểu rõ hoạt động của phương pháp này là một nhu cầu giúp cho các kỹ sư thiết kế kiến trúc phần mềm có thể có một chất liệu phục vụ cho việc xây dựng hệ thống phần mềm của chính mình. Tổng quan về notifier-call chain Notifier-call chain là một danh sách liên kết đơn của các hàm sẽ được thực hiện tuần tự khi một sự kiện nhất định xảy ra. Mỗi hàm có chức năng giúp cho một sub-system nhất định biết về sự kiện và có hành động tương ứng đối với sự kiện đó. Như vậy notifi-call chain bao gồm hai thành phần, bên chủ động thông báo sự ...