Brief introducing about Basic Hardware

Bộ nhớ chính và các thanh ghi là những thiết bị lưu trữ duy nhất mà CPU có thể truy cập trực tiếp để lấy dữ liệu. Bất kì instruction hay các data được sử dụng bởi các instruction đều phải được lưu trữ trong một trong 2 thiết bị này. Nếu dữ liệu không nằm trong bộ nhớ, nó cần phải được nạp vào đó trước khi được CPU sử dụng.

Nguyên tắc là mỗi process đều cần có một vùng nhớ riêng biệt, và chúng ta phải luôn luôn đảm bảo điều này. Để thực hiện, chúng ta phải xác định được phạm vi địa chỉ bộ nhớ hợp lệ mà process có thể truy cập vào đó, và đảm bảo process đó chỉ có thể truy cập vào các địa chỉ ô nhớ này mà thôi. Chúng ta thực hiện những yêu cầu trên nhờ vào việc sử dụng 2 thanh ghi, một là base và thanh ghi thứ 2 là limit. Base register được dùng để lưu trữ địa chỉ ô nhớ hợp lệ nhỏ nhất, còn limit register sẽ lưu trữ kích thước của cả ô nhớ. Ví dụ, nếu thanh ghi base lưu trữ giá trị 300040 và thanh ghi limit lưu trữ giá trị là 120900 thì chương trình sẽ có thể truy cập hợp lệ vào các địa chỉ ô nhớ trong khoảng từ 300040 cho đến 420940.

Thanh ghi base và limit chỉ có thể được nạp bởi hệ điều hành. Nhờ đó, nội dung của 2 thanh ghi này chỉ có thể được thay đổi bởi hệ điều hành, ngăn chặn sự can thiệp trái phép của các chương trình người dùng vào những thanh ghi này.

Kiểm tra đảm bảo việc truy cập địa chỉ ô nhớ hợp lệ được điều khiển bởi CPU

Address Binding – Kết nối địa chỉ ô nhớ

Thông thường, các chương trình lưu trữ trên đĩa cứng đều là dạng file thực thi nhị phân (binary executable file). Để có thể được thực thi thì chương trình phải được mang vào bộ nhớ chính, một process sẽ được tạo ra để đảm nhận việc thực thi này. Tùy thuộc vào trình quản lí bộ nhớ được sử dụng trong hệ thống, process có thể được di chuyển qua lại giữa disk và memory trong suốt quá trình thực thi của nó. Khái niệm input queue được tạo ra để chỉ những process nằm trên đĩa cứng và đang chờ để được đưa vào memory để thực thi.

Địa chĩ ô nhớ có thể được biểu diễn dưới nhiều kiểu khác nhau theo các bước như sau. Các địa chỉ trong source code có dạng symbolic (kí tự – là các biến, hằng, con trỏ…). Trình biên dịch sẽ tự động kết nối các symbolic address này vào relocatable address. Tiếp theo, bộ linker hoặc loader sẽ lại liên kết các địa chỉ khả tái định vị này tới cá địa chỉ tuyệt đối – chính là địa chỉ thực của bộ nhớ. Quá trình liên kết giữa các mã lệnh và dữ liệu tới các địa chỉ ô nhớ có thể được hoàn thành tại bất kì các bước nào trong quy trình sau:

  • Compile time. Nếu tại bạn biết chính xác vị trí mà process sẽ được lưu trữ trong bộ nhớ chính tại compile time thì absolute code – trỏ thẳng tới absolute address sẽ được tạo ra. Ví dụ, nếu bạn biết process sẽ bắt đầu thực thi tại vị trí R, thì compiler sẽ sinh ra đoạn code bắt đầu tại đúng vị trí đó. Nếu sau này, vị trí R bị biến thành R’ thì bạn cần phải recompile lại chương trình.
  • Load time. Nếu không biết vị trí process bắt đầu thực thi trong bộ nhớ tại compile time, compiler sẽ phải tạo ra relocatable code – sử dụng relocatable address. Trong trường hợp này, việc liên kết giữa relocatable address và absolute address sẽ bị trì hoãn cho tới thời điểm nạp – load time. Nếu địa chỉ bắt đầu thay đổi, chúng ta chỉ cần reload user code.
  • Execution time. Nếu process bị di chuyển trong khi đang thực thi từ một vùng nhớ (memory segment) này sang một vùng nhớ khác, việc kết nối địa chỉ sẽ bị trì hoãn cho tới run time.

    Từng bước trong quá trình thực thi process

Logical vs Physical address space.

Một địa chỉ được tạo ra bởi CPU thông thường được là địa chỉ luận lý – logical address, còn địa chỉ thực sự của bộ nhớ – địa chỉ được nạp vào thanh ghi memory-address của bộ nhớ – thường được gọi là địa chỉ vật lý – physical address.

Các phương pháp liên kết địa chỉ bộ nhớ (address-binding method) tại compile-time và load-time sẽ tạo ra địa chỉ logic và physic giống như nhau. Tuy nhiên, execution-time address-binding method lại trả về giá trị địa chỉ vật lý và luận lý khác nhau. Trong trường hợp này, chúng ta gọi các logical address này là virtual address.

Việc chuyển từ địa chỉ ảo sang địa chỉ thực tại run-time được thực hiện nhờ một thiết bị phần cứng gọi là memory-management unit (MMU). Hình ảnh dưới đây sẽ mô tả đơn giản việc sử dụng MMU trong quá trình chuyển đổi từ virtual address sang physical address.

Tất cả các user program đều không bao giờ nhìn thấy địa chỉ vật lý thực của bộ nhớ, chúng chỉ làm việc với logical address mà thôi. Việc chuyển đổi từ logical address sang physical address được thực thi bởi phần cứng. Vị trí cuối cùng của địa chỉ bộ nhớ vật lý được trỏ đến sẽ không được xác định cho tới khi hoàn tất việc chuyển đổi.

Dynamic Loading

Kích thước của process sẽ ảnh hưởng đến kích thước trống của bộ nhớ chính. Nếu toàn bộ chương trình và dữ liệu mà process cần dùng cho quá trình thực thi của mình được nạp vào bộ nhớ thì kích thước của process sẽ trở nên rất lớn. Để có thể sử dụng hiệu quả bộ nhớ, chúng ta sử dụng một phương pháp gọi là dynamic loading. Cơ chế của nó là, một routine (một công đoạn, thành phần mà process cần trong quá trình thực thi của nó) sẽ không được nạp vào bộ nhớ cho tới khi được gọi. Tất cả các routine đều được lưu trữ trên đĩa cứng với định dạng format là relocatable load. Ban đầu sẽ chỉ có chương trình chính được nạp vào bộ nhớ và thực thi. Khi một routine cần gọi một routine khác, quá trình gọi routine này sẽ check xem routine khác đó đã từng được nạp vào hay chưa, nếu chưa thì sẽ thực hiện việc nạp vào bộ nhớ và update bảng địa chỉ bộ nhớ của chương trình.

Lợi ích của dynamic loading đó là các routine không được sử dụng sẽ không bao giờ được nạp, nhờ đó tiết kiệm được không gian bộ nhớ. Dynamic loading không yêu cầu sự hỗ trợ đặc biết nào từ OS. Việc tận dụng lợi thế của phương thức này tùy thuộc vào lập trình viên. Hệ điều hành sẽ chỉ cung cấp một số thư viện hỗ trợ.

Dynamic Linking and Shared Libraries

Một vài hệ điều hành chỉ hỗ trợ static linking, các hệ điều hành này xem các system language libraries như bất kì object module khác và tất cả đều được combine bởi bộ loader thành một binary program image. Nội dung của dynamic linking cũng tương tự như dynamic loading. Tính năng này thường được sử dụng với system libraries, ví dụ như language subroutine libraries. Nếu không có tính năng này, mỗi chương trình trong hệ thống phải đính kèm một bản copy của language library mà nó sử dụng (hay tối thiểu là các routine mà program refer đến) trong executable image. Việc này sẽ làm hao phí cả bộ nhớ chính và đĩa cứng.

Với dynamic linking, ứng với mỗi library routine reference sẽ có một stub được đính kèm vào program image. Stub là một đoạn code nhỏ cho biết cách xác định chính xác memory-resident library routine (nơi lưu trữ library của routine) hoặc cách để nạp thư viện nếu routine đó chưa được giới thiệu. Khi stub được thực thi, nó kiểm tra xem liệu routine đang cần có nằm trong memory hay chưa. Nếu không, chương trình sẽ load routine đó vào bộ nhớ chính. Ngược lại, địa chỉ của routine sẽ thay thế cho chính stub đang được thực thi đó và bắt đầu thực thi routine. Như vậy, trong lần gọi tiếp theo, library routine sẽ được thực thi trực tiếp và sẽ không đòi hỏi chi phí để thực hiện dynamic linking nữa.

Tính năng này còn có thể được mở rộng ra cho việc cập nhật thư viện, ví dụ như khi fix bug chẳng hạn. Một thư viện có thể được thay thể bằng một phiên bản mới, và tất cả các chương trình refer đến thư viện đó sẽ tự động sử dụng phiên bản mới này. Nếu không có dynamic linking, tất cả các chương trình đó sẽ phải được relink để có thể truy cập vào library. Một library có thể được load nhiều version khác nhau vào bộ nhớ chính, và các chương trình sử dụng library đó có thể dựa vào version information để quyết định sử dụng version nào. Những cập nhật nhỏ sẽ không thay đổi version number, còn những thay đổi lớn sẽ tăng version number. Các chương trình khác nếu liên kết tới thư viện trước khi thư viện mới được cài đặt sẽ tiếp túc sử dụng thư viện cũ. Cơ chế này còn được gọi là shared libraries.

Không như dynamic loading, dynamic linking cần có trợ giúp từ hệ điều hành. Khi bộ nhớ của một process đang sử dụng được bảo vệ khỏi sự truy cập từ các process khác thì vai trò của hệ điều hành lúc này chỉ là kiểm tra xem liệu routine cần sử dụng có đang nằm trong vùng nhớ của một process nào khác hay đang nằm trong vùng địa chỉ ô nhớ cho phép nhiều process được truy cập cùng lúc.