bo nho trong 2
IV. Sử dụng không gian hoán đổi
Một không gian hoán đổi đã khởi tạo sẽ được lấy để sử dụng nhờ lệnh
swapon. Lệnh này báo cho kernel rằng không gian hoán đổi có thể được sử
dụng. Đường dẫn đến không gian hoán đổi được cấp như là đối số, vì vậy để
bắt đầu hoán đổi trên một file swap tạm thời, bạn có thể sử dụng đoạn lệnh
sau :
$ swapon /extra-swap
$
Không gian hoán đổi có thể được sử dụng tự động bằng cách liệt kê chúng
trong file /etc/fstab
/dev/hda8
/swapfile
none
none
swap
swap
sw
sw
0
0
0
0
Đoạn mã khởi động sẽ chạy lệnh swapon -a, lệnh này sẽ bắt đầu thực hiện
hoán đổi trên tất cả các không gian hoán đổi được liệt kê trong file
/etc/fstab. Do đó lệnh swapon chỉ thường được sử dụng khi cần hoán đổi
thêm.
Bạn có thể quản lý việc sử dụng không gian hoán đổi với lệnh free. Nó sẽ
cho biết tổng số không gian hoán đổi được sử dụng
$ free
total
used
free
shared
buffers
Mem:
15152
14896
256
12404
2528
-/+ buffers:
12368
2784
Swap:
32452
6684
25768
$
Một không gian hoán đổi có thể bị loại bỏ bằng lệnh swapoff. Thông thường
không cần phải dùng lệnh này ngoại trừ đối với không gian hoán đổi tạm
thời. Bất kì trang nào đang được sử dụng trong không gian hoán đổi đều
được đưa vào trước. Nếu không có đủ bộ nhớ vật lý để chứa chúng thì chúng
sẽ được đưa ra đến một không gian hoán đổi khác. Nếu không có đủ bộ nhớ
ảo để chứa tất cả các trang, Linux sẽ bắt đầu dừng lại (thrash), sau một
khoảng thời gian dài nó sẽ khôi phục nhưng trong lúc ấy hệ thống không thể
sử dụng. Bạn nên kiểm tra ( bằng lệnh free ) để xem có đủ bộ nhớ trống
không trước khi loại bỏ một không gian hoán đổi.
Tất cả không gian hoán đổi được sử dụng tự động nhờ lệnh swapon -a đều
có thể được loại bỏ với lệnh swapoff -a, lệnh này tìm thông tin trong file
/etc/fstab để loại bỏ. Còn các không gian hoán đổi được điều khiển bằng tay
thì vẫn sử dụng bình thường.
Đôi khi có nhiều không gian hoán đổi được sử dụng mặc dù có nhiều bộ nhớ
vật lý trống. Điều này có thể xảy ra nếu tại một thời điểm có nhu cầu về
hoán đổi, nhưng sau đó một tiến trình lớn chiếm nhiều bộ nhớ vật lý kết thúc
và giải phóng bộ nhớ. Dữ liệu đã đưa ra sẽ không tự động đưa vào cho đến
khi nó cần, vì vậy bộ nhớ vật lý sẽ còn trống trong một thời gian dài. Bạn
không cần phải lo lắng về điều này mà chỉ cần biết điều gì đang xảy ra.
V. Định vị không gian hoán đổi
Người ta thường nói rằng bạn nên định vị không gian hoán đổi gấp đôi bộ
nhớ vật lý, nhưng đây không phải là một quy luật đúng. Bạn hãy xem cách
làm đúng sau đây :
+ Dự đoán tổng bộ nhớ mà bạn cần. Đây là số lượng bộ nhớ lớn nhất mà
bạn cần tại một thời điểm nào đó, là tổng bộ nhớ cần thiết cho tất cả các
chương trình mà bạn muốn chạy cùng một lúc.
Lệnh free và ps sẽ có ích để đoán lượng bộ nhớ cần dùng.
+ Cộng thêm một ít vào dự đoán ở bước 1, bởi vì dự đoán về kích thước
các chương trình có thể sai do bạn quên một số chương trình mà bạn muốn
chạy, và để chắc chắn bạn nên chuẩn bị một không gian phụ để dùng khi
cần. Nên định vị dư hơn là thiếu nhưng không dư nhiều quá sẽ gây lãng phí.
Bạn cũng nên làm tròn lên thành một số chẵn megabyte.
+ Dựa trên những tính toán trên, bạn biết sẽ cần tổng cộng bao nhiêu bộ
nhớ. Vì vậy, để định vị không gian hoán đổi, bạn chỉ cần lấy tổng bộ nhớ sẽ
dùng trừ cho bộ nhớ vật lý.
+ Nếu không gian hoán đổi mà bạn đã tính lớn hơn hai lần bộ nhớ vật lý
thì bạn nên mua thêm RAM, nếu không hiệu năng của máy sẽ thấp.
Tốt hơn hết là nên có một vài không gian hoán đổi cho dù theo sự tính toán
của bạn là không cần. Linux sử dụng không gian hoán đổi khá linh hoạt.
Linux sẽ đưa các trang nhớ không sử dụng ra ngoài cho dù bộ nhớ chưa cần
dùng. Điều này giúp tránh việc chờ đợi hoán đổi khi cần bộ nhớ.
CHƯƠNG IV
CƠ CHẾ QUẢN LÝ BỘ NHỚ VẬT LÝ,
ÁNH XẠ BỘ NHỚ
I. Quản lý bộ nhớ vật lý
1. Bộ định vùng
Các bảng trang kernel ánh xạ tối đa bộ nhớ vật lý vào dãy địa chỉ bắt đầu tại
PAGE_OFFSET. Các trang vật lý chiếm bởi đoạn mã và dữ liệu kernel sẽ
được dành riêng và không sử dụng cho bất kì mục đích nào khác. Các trang
vật lý khác được định vị cho bộ nhớ ảo tiến trình, bộ nhớ đệm, bộ nhớ ảo
kernel, ... khi cần. Để làm được điều này, chúng ta phải có cách theo dõi
trang vật lý nào được sử dụng và được sử dụng bởi ai.
Bộ định vùng ( zone allocator ) quản lý bộ nhớ vật lý. Bất kì mã kernel nào
đều có thể gọi tới bộ định vùng thông qua hàm alloc_pages() và được cấp
một khối gồm 2n trang được canh trên một đường biên tương ứng. Chúng ta
cũng có thể chuyển khối các trang này lại cho bộ định vùng nhờ hàm
free_pages(). Số mũ n được gọi là thứ tự của sự định vùng. Các khối không
cần phải được giải phóng giống cách mà chúng được xác định, nhưng phải
được giải phóng chính xác, và khung trang đầu tiên của khối phải có một số
tham chiếu khác 0. Ví dụ, khi bạn yêu cầu một khối 8 trang, sau đó giải
phóng một khối 2 trang trong khối 8 trang đó, muốn làm điều này trước hết
bạn phải tăng biến tham chiếu của trang đầu tiên trong khối 2 trang. Lý do là
vì khi bạn định vị một khối 8 trang, chỉ có biến tham chiếu của trang đầu tiên
trong khối được tăng, mặt khác đoạn mã giải phóng trang sẽ không giải
phóng một trang có biến tham chiếu là 0, nên biến tham chiếu của các trang
khác phải điều khiển bằng tay. Việc tăng tất cả các biến tham chiếu của các
trang trong khối là không cần thiết đồng thời làm tăng kích thước đoạn mã
định vị trang.
2. Các vùng
Các dãy trang vật lý khác nhau thì có các thuộc tính khác nhau, phục vụ cho
các mục đích của kernel. Ví dụ, DMA ( Direct Memory Access ) cho phép
các thiết bị ngoại vi đọc và viết dữ liệu trực tiếp lên RAM mà không có sự
can thiệp của CPU, chỉ có thể làm việc với địa chỉ vật lý nhỏ hơn 16MB.
Một vài hệ thống có bộ nhớ vật lý nhiều hơn có thể được ánh xạ giữa
PAGE_OFFSET và 4GB, những trang vật lý này không thể truy cập trực
tiếp đến kernel, vì vậy chúng phải được quản lý theo cách khác. Bộ định
vùng quản lý những sự khác nhau như vậy bằng cách chia bộ nhớ thành các vùng và xem mỗi vùng là một đơn vị cho sự định vị.
Cấu trúc dữ liệu root được quản lý bởi bộ định vùng là zone_struct, gồm tập
hợp tất cả dữ liệu liên quan đến việc quản lý một vùng cụ thể.
Zonelist_struct bao gồm một mảng các con trỏ zone_struct và một gfp_mask
chỉ ra cơ chế định vị nào có thể sử dụng zone_list. Zone_struct offset chỉ ra
địa chỉ offset của nơi bắt đầu một vùng trong bộ nhớ vật lý.
II. Ánh xạ bộ nhớ (mm3 - Memory Mapping)
Khi một ảnh được thực hiện, nội dung của nó phải được đưa vào không gian
địa chỉ ảo tiến trình. File thực thi không thực sự được mang vào bộ nhớ vật
lý, mà thay vào đó nó chỉ đơn thuần được liên kết đến bộ nhớ ảo tiến trình.
Sau đó ảnh được mang vào bộ nhớ như là một phần của chương trình được
tham chiếu bởi ứng dụng đang chạy. Sự liên kết một ảnh vào không gian địa
chỉ ảo tiến trình được gọi là ánh xạ bộ nhớ
Mỗi bộ nhớ ảo tiến trình được miêu tả bằng một cấu trúc dữ liệu mm_struct.
Cấu trúc này chứa thông tin về ảnh hiện đang thực thi và các con trỏ đến một
số cấu trúc dữ liệu vm_area_struct. Mỗi cấu trúc dữ liệu vm_area_struct mô
tả điểm bắt đầu và kết thúc của khu vực bộ nhớ ảo, quyền truy cập của các
tiến trình đến bộ nhớ đó và các hoạt động của bộ nhớ đó. Các hoạt động này
là tập hợp các thường trình (routine) mà Linux phải sử dụng khi xử lý khu
vực bộ nhớ ảo này. Ví dụ, một trong những hoạt động của bộ nhớ ảo là thực
hiện các hiệu chỉnh khi một tiến trình tìm cách truy cập vào bộ nhớ ảo mà
không có giá trị tương ứng trong bộ nhớ vật lý ( thông qua lỗi trang ). Hoạt
động này là hoạt động nopage. Hoạt động nopage được sử dụng khi Linux
yêu cầu các trang của một ảnh thực thi trong bộ nhớ.
Khi một ảnh thực thi được ánh xạ vào địa chỉ ảo tiến trình, một tập hợp các
cấu trúc dữ liệu vm_area_struct được thực hiện. Mỗi cấu trúc dữ liệu
vm_area_struct mô tả một phần của ảnh thực thi, đoạn mã thực thi, dữ liệu
được khởi tạo ( là các biến ), dữ liệu không được khởi tạo,... Linux cũng hỗ
trợ một số các hoạt động bộ nhớ ảo chuẩn.
CHƯƠNG V
CẤP PHÁT VÀ GIẢI PHÓNG VÙNG NHỚ
I. Cấp phát vùng nhớ
1. Cấp phát vùng nhớ giản đơn
Vùng nhớ giản đơn là vùng nhớ có kích thước nhỏ hơn kích thước của bộ
nhớ vật lý. Chúng ta cấp phát vùng nhớ cho tiến trình dựa vào hàm malloc()
của thư viện C chuẩn.
void *malloc(size_t size);
Không giống như trên DOS và Windows, khi sử dụng hàm này, chúng ta
không cần phải khai báo thư viện malloc.h
size : kích thước vùng nhớ muốn cấp phát, size có kiểu size_t. Thực
sự size_t được định nghĩa là kiểu nguyên không dấu unsigned int.
Ví dụ : memory1.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#define Kich_thuoc(1024*1024) //1 Mb bộ nhớ
/*-------------------------------------------------*/
int main( ){
char *some_memory;
int megabyte=Kich_thuoc;
int exit_code=EXIT_FAILURE;
some_memory=(char*) malloc(megabyte);
if(some_memory != NULL){
sprintf(some_memory,"Cap phat vung nho gian don ");
printf("%s",some_memory);
exit_code=EXIT_SUCCESS;
}
exit(exit_code)
}
Chương trình yêu cầu hàm malloc( ) cấp phát và trả về con trỏ trỏ đến vùng
nhớ 1 MB. Chương trình kiểm tra con trỏ Null để đảm bảo hàm malloc( )
cấp phát thành công. Tiếp theo chuỗi "Cap phat vung nho gian don" được
đặt vào phần đầu của vùng nhớ. Tuy chuỗi " Cap phat vung nho gian don"
không chiếm hết 1 MB vùng nhớ nhưng hàm malloc( ) vẫn cấp phát vùng
nhớ đúng bằng kích cỡ chương trình yêu cầu.
Hàm malloc( ) trả về con trỏ *void( con trỏ dạng tổng quát), vì vậy trong
chương trình cần phải chuyển về con trỏ dạng char* để truy cập đến vùng
nhớ theo dạng chuỗi kí tự. Hàm malloc( ) bảo đảm vùng nhớ cấp phát là một
dãy các byte xếp liền nhau. Vì lý do này, có thể định nghĩa kiểu trả về là bất
kì một con trỏ dữ liệu nào.
2. Cấp phát vùng nhớ lớn
Vùng nhớ lớn có thể có kích thước vượt hơn kích thước thật của bộ nhớ vật
lý. Do chưa biết hệ thống có chấp nhận hay không nên trong quá trình xin
cấp phát, có thể hệ thống sẽ ngắt ngang nếu bộ nhớ cạn kiệt.
Ví dụ : memory2.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#define Kich_thuoc(1024*1024) //1 Mb bộ nhớ
/*-------------------------------------------------*/
int main( ){
char *some_memory;
size_t size_to_allocate= Kich_thuoc;
int megs_obtained=0;
while( megs_obtainde<16){
some_memory=(char *)malloc(size_ro_allocate);
if (some_memory != NULL){
megs_obtained ++;
sprintf(some_memory,"Cap phat vung nho lon");
printf("%s-now
allocated%dMegabytes
",some_memory,megs_obtained);
}
else{
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}
Chương trình này tương tự như chương trình cấp phát vùng nhớ giản đơn.
Nó thực hiện vòng lặp và yêu cầu cấp phát liên tục vùng nhớ ( đến 512 Mb).
Chương trình chạy hoàn toàn tốt đẹp và chỉ chạy trong chớp mắt. Rõ ràng hệ
thống có khả năng đáp ứng nhu cầu xin cấp phát vùng nhớ khá lớn, lớn hơn
cả vùng nhớ vật lý có sẵn trên máy.
Tuy nhiên, chúng ta hãy xem trong lần cấp phát vùng nhớ ở chương trình
memory3.c này, khả năng đáp ứng của hệ thống còn đủ hay không. Chương
trình memory3.c chỉ xin cấp phát mỗi lần 1Kb bộ nhớ và ghi dữ liệu vào đó.
Chương trình này thực sự có thể làm cạn kiệt tài nguyên hay chậm đi cả hệ
thống, bạn nên đóng tất cả ứng dụng trước khi chạy thử.
Ví dụ : memory3.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#define ONE_K(1024)
/*------------------------------------------------------------------*/
int main(){
char *some_memory;
int size_to_allocate= ONE_K;
int megs_obtained=0;
int ks_obtained=0;
while(1){
for (ks_obtained = 0; ks_obtained < 1024; ks_obtained++){
some_memory= (char *)malloc(size_to_allocate);
if (some_memory == NULL) exit (EXIT_FAILURE);
sprintf(some_memory, "Hello world");
}
megs_obtained++;
printf("Now allocated %d Megabytes
", megs_obtained);
}
exit(EXIT_SUCCESS);
}
Lần này chỉ cấp phát được 154Mb là chương trình chấm dứt. Hơn nữa
chương trình này chạy chậm hơn memeory2.c. Tuy nhiên, bộ nhớ xin cấp
phát vẫn có khả năng lớn hơn bộ nhớ vật lý có sẵn.
Bộ nhớ mà ứng dụng yêu cầu phân bổ được quản lý bởi hạt nhân Linux và
UNIX. Mỗi lần chương trình yêu cầu vùng nhớ hoặc cố đọc ghi vào vùng
nhớ được phân bổ trước đó, hạt nhân Linux sẽ theo dõi và quyết định xem
cần xử lý yêu cầu này như thế nào.
Khởi đầu, hạt nhân hoàn toàn có thể đơn giản sử dụng vùng nhớ vật lý còn
trống để thỏa mãn yêu cầu về vùng nhớ cho ứng dụng. Tuy nhiên , khi vùng
nhớ vật lý bị đầy hay đã cấp phát hết, hạt nhân bắt đầu dùng đến không gian
hoán đổi. Trên hầu hết các phiên bản của Linux và UNIX, vùng không gian
hoán đổi này nằm riêng biệt trên một phân vùng. Hạt nhân thực hiện việc di
chuyển dữ liệu và mã lệnh của chương trình từ vùng nhớ vật lý ra không
gian tráo đổi và ngược lại.
Khi ứng dụng yêu cầu cấp phát vùng nhớ mà không gian tráo đổi lẫn bộ nhớ
vật lý thật đã đầy thì hệ thống không thể cấp phát và hoán chuyển bộ nhớ
được nữa.
Trong quá trình xin cấp phát vùng nhớ, cần phải đặc biệt lưu ý đến số lần và
kích thước vùng nhớ xin cấp phát. Điều này sẽ ảnh hưởng đến hiệu quả vùng
nhớ được cấp phát. Ví dụ nếu yêu cầu cấp phát 10 lần , mỗi lần 1 Kb, có thể
thu được 10 khối nhớ rời nhau , mỗi khối vẫn đảm bảo 1 Kb. Tuy nhiên để
tăng hiệu quả có thể yêu cầu cấp phát một lần với kích thước khối nhớ là 10
Kb xếp liên tục gần nhau. Hệ điều hành quản lý các khối nhớ cấp phát theo
danh sách liên kết . Nếu các khối nhớ nhỏ và rời rạc thì danh sách liên kết
này sẽ lớn và chiếm nhiều không gian và thời gian quản lý hơn.
3. Vùng nhớ được bảo vệ
Mặc dù tiến trình được dành cho 4 Gb không gian địa chỉ nhưng tiến trình
cũng không thể đọc/ghi dữ liệu tùy tiện nếu chưa xin HĐH cấp phát. Bạn chỉ
có thể chép được dữ liệu vào vùng nhớ mà HĐH cấp phát thông qua
malloc(). Nếu tiến trình cố gắng đọc hay ghi dữ liệu vào vùng nhớ mà chưa
được cấp phát, HĐH sẽ quyết định cắt ngang chương trình với lỗi trang hay
còn gọi là lỗi phân đoạn (segment fault) cho dù vùng nhớ đó vẫn nằm trong
không gian địa chỉ 4 Gb. Chương trình sau xin cấp phát vùng nhớ 1024 bytes
rồi ghi dữ liệu vào từng byte. Ta cố ý ghi dữ liệu vào byte cuối cùng của
vùng nhớ (byte thứ 1025).
Ví dụ : memory4.c
#include<unistd.h>
#include<stdlib.h>
#define ONE_K(1024)
/*------------------------------------------------------------------*/
int main(){
char *some_memory;
char *scan_ptr;
int count=0;
some_memory= (char *)malloc(ONE_K);
if (some_memory==NULL) exit (EXIT_FAILURE);
scan_ptr= some_memory;
while(1){
prinf("write byte %d", ++count);
*scan_ptr ='\0';
scan_ptr ++;
}
exit(EXIT_SUCCESS);
}
Khi chương trình bị lỗi hệ thống , HĐH sẽ ghi toàn bộ ảnh của tiến trình
trong bộ nhớ ( bao gồm các chỉ thị lệnh thực thi bị lỗi ) xuống đĩa với tên file
là core. Có thì dùng file này với các chương trình debug để biết nguyên nhân
sinh ra lỗi.
4. Một số hàm cấp phát vùng nhớ khác
a. Hàm calloc()
Hàm này không được sử dụng phổ biến như hàm malloc( ). Hàm cũng cho
phép một vùng nhớ mới được cấp phát nhưng nó được thiết kế phân bổ vùng
nhớ cho một mảng cấu trúc (table)
void *calloc( size_t nmemb, size_t size);
Hàm này yêu cầu các tham số khó hơn hàm malloc( ):
+ tham số nmemb là số phần tử của mảng (số ô trong table)
+ tham số size chỉ thị kích cỡ của mỗi phần tử trong mảng (kích
thước một ô trong table).
Vùng nhớ cấp phát được khởi tạo với giá trị zero. Nếu thành công , giá trị trả
về của hàm là con trỏ trỏ đến phần tử đầu tiên của mảng ngược lại hàm trả
về giá trị NULL. Tương tự như hàm malloc( ) mỗi lần gọi tiếp theo, calloc( )
không đảm bảo sẽ cung cấp cho chương trình vùng nhớ liên tục với lời gọi
trước đó.
b. Hàm realloc( )
Hàm này cũng không được sử dụng phổ biến. Hàm chỉnh sửa kích cỡ của
một khối nhớ được cấp phát trước đó.
void *realloc(void *ptr, size_t size);
Hàm này nhận đối số là con trỏ ptr trỏ đến vùng nhớ trả về bởi các hàm cấp
phát như malloc( ), calloc( ), thậm chí kể cả hàm realloc( ), sau đó thực hiện
co giãn hay tăng giảm khối nhớ theo kích thước size chỉ định . Hàm đảm bảo
vùng nhớ mới đạt kích thước như yêu cầu .
Nếu thành công, giá trị trả về của hàm là con trỏ trỏ đến vùng nhớ đã thay
đổi kích thước. Một điều lưu ý là trong chương trình nên dùng một con trỏ
khác để nhận kết quả do hàm realloc( ) trả về, không bao giờ sử dụng lại con
trỏ chuyển cho realloc( ) trước đó. Ngược lại , nếu vùng nhớ không thể thay
đổi kích thước như yêu cầu , hàm sẽ trả về con trỏ NULL. Do đó nếu gán
cho giá trị trả về của hàm realloc( ) là một con trỏ đang sử dụng thì khi hàm
không thành công, nó sẽ trả về giá trị NULL và vùng nhớ con trỏ trỏ đến
trước đó sẽ bị thất lạc.
II. Giải phóng vùng nhớ
Đối với các tiến trình chỉ yêu cầu cấp phát vùng nhớ nhỏ, sử dụng chúng
trong một thờii gian ngắn và kết thúc thì HĐH sẽ tự động giải phóng vùng
nhớ khi tiến trình kết thúc. Tuy nhiên hầu hết các tiến trình đều có nhu cầu
cấp phát vùng nhớ động khá lớn, đối với các yêu cầu cấp phát này , HĐH
không hỗ trợ việc giải phóng vùng nhớ, do đó chương trình luôn cần phải
giải phóng vùng nhớ khi không dùng đến nữa bằng cách sử dụng hàm free( )
void free( void *ptr_to_memory);
Khi sử dụng khai báo kèm theo thư viện stdlib.h
* ptr_to_memory là con trỏ trỏ đến vùng nhớ cần giải phóng. Thường con
trỏ này do hàm malloc( ), calloc( ) hoặc realoc( ) trả về.
Ví dụ : memory5.c
#include <stdlib.h>
#define ONE_K(1024)
int main(){
char*some_memory;
int exit_code=EXIT_FAILURE;
some_memory=(char*)malloc(ONE_K)
if(some_memory!=NULL){
free(some_memory);
exit_code=EXIT_SUCCESS ;
}
exit(exit_code)
}
Sau khi vùng nhớ được giải phóng, thì con trỏ trỏ đến vùng nhớ đã giải
phóng không thể sử dụng được nữa (không thể đọc/ghi) do hệ thống đã loại
bỏ vùng nhớ khỏi sự quản lý của HĐH. Mọi thao tác đọc ghi trên vùng nhớ
đã giải phóng sẽ gây ra lỗi .
III. Truy xuất con trỏ NULL
Linux bảo vệ rất chặt chẽ việc đọc/ghi trên con trỏ NULL. Do Linux sử dụng
thư viện chuẩn GNU (hàm sprintf và printf) nên việc đọc con trỏ NULL là
được phép. Thư viện GNU đơn giản trả về cho bạn chuỗi "null", tuy nhiên
ghi vào con trỏ Null là bị cấm.
Ví dụ : memory6.c
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
int main() {
char *some_memory=(char*)0;
printf("doc tu con tro null %s
", some_memory);
sprintf(some_memory,"ghi vao con tro null");
exit (EXIT_SUCCESS);
}
Nếu không sử dụng các hàm thư viện GNU thì việc đọc trên con trỏ Null
cũng bị cấm.
Bạn đang đọc truyện trên: TruyenTop.Vip