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 và 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 MappingKỹ 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.
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);
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
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
Post a Comment