Bài 1: TỔNG QUAN VỀ LẬP TRÌNH HỆ
THỐNG
Khái niệm về lập trình hệ thống
Lập trình hệ thống (hoặc chương trình hệ thống) là hoạt động của các phần mềm hệ
thống. Đầu tiên chỉ ra sự khác biệt tiêu biểu của các chương trình hệ thống khi đã so
sánh tới lập trình ứng dụng là ở đó nhắm vào lập trình ứng dụng để sản sinh phần mềm
mà cung cấp những dịch vụ tới người dùng (ví du: bộ xử lý văn bản), trong khi những
nhà lập trình hệ thống nhắm vào việc sản xuất phần mềm mà cung cấp những dịch vụ
tới phần cứng máy tính (ví dụ: phần mềm chống phân mảnh đĩa). Nó cũng yêu cầu một
độ lớn hơn của sự ý thức phần cứng.
371 trang |
Chia sẻ: phuongt97 | Lượt xem: 808 | Lượt tải: 0
Bạn đang xem trước 20 trang nội dung tài liệu Bài giảng Lập trình hệ thống, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
hất có thể.
Hàm SetupDevice là khá đơn giản :
VOID SetupDevice(PDEVICE_EXTENSION pdx)
{
WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR),
INTCSR_IMBI_ENABLE
│ (INTCSR_MB1 << INTCSR_IMBI_REG_SELECT_SHIFT)
│ (INTCSR_BYTE0 << INTCSR_IMBI_BYTE_SELECT_SHIFT)
);
}
Hàm này lập trình lại INTCSR để chỉ định điều chúng ta muốn một ngắt được xảy ra khi
có sự thay đổi tới byte 0 của thanh ghi hộp thư về 1. chúng ta có thể chỉ định các điều
kiện ngắt khác cho chip này, bao gồm sự “trống rỗng” của một byte riêng biệt của một
thanh ghi hộp thư đến được chỉ định, việc hoàn thành một tiến trình chuyển DMA đọc,
và việc hoàn thành của một tiến trình chuyển DMA ghi
Bắt đầu một thao tác đọc -Starting a Read Operation
PCI42’s StartIo routine cho phép “mô hình” mà chúng ta đã được học rồi.
VOID StartIo(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
251/369
PDEVICE_EXTENSION pdx =
(PDEVICE_EXTENSION) fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
if (!stack->Parameters.Read.Length)
{
StartNextPacket(&pdx->dqReadWrite, fdo);
CompleteRequest(Irp, STATUS_SUCCESS, 0);
return;
}
pdx->buffer = (PUCHAR) Irp->AssociatedIrp.SystemBuffer;
pdx->nbytes = stack->Parameters.Read.Length;
pdx->numxfer = 0;
KeSynchronizeExecution(pdx->InterruptObject,
(PKSYNCHRONIZE_ROUTINE) TransferFirst, pdx);
}
1. Ở đây chúng ta ghi lại các thông số trong phần mở rộng thiết bị để mô tả tiến
trình vào của một thao tác đầu vào. Chúng ta bảo đảm PCI42 sử dụng phương thức
DO_BUFFERED_IO, là không điển hình nhưng nó giúp chúng ta tạo ra trình điều khiển
đủ đơn giản để được sử dựng như một ví dụ.
2. Bởi vì ngắt của chúng ta đã được kết nối, thiết bị của chúng ta có thể ngắt bất kỳ lúc
nào. ISR sẽ muốn truyền các bytes dữ liệu khi các ngắt xảy ra, nhưng chúng ta muốn
được đảm bảo rằng ISR không bao giờ bị lộn xộn, rắc rối về bộ đệm dữ liệu để sử dụng
hoặc về số các bytes chúng ta đang cố gắng đọc. Để kiềm chế “tính hám” của ISR, chúng
ta đặt một cờ trong phần mở rộng thiết bị định bận rộn mà thông thường là FALSE.
252/369
Bây giờ là lúc thiết lập cờ về giá trị TRUE. Giống như thông thường khi chúng ta giải
quyết với một tài nguyên chia sẻ , chúng ta cần phải đồng bộ việc thiết lập cờ với đoạn
mã trong ISR mà kiểm tra nó, và theo đó cần phải viện dẫn một thủ tục SynchCritSection
giống như tôi đã thảo luận từ trước. Ngòai ra, có thể xảy ra một byte dữ liệu đã có sẵn
rồi. trong trường hợp đó thì ngắt đầu tiên sẽ không bao giờ xảy ra . TransferFirst là một
thủ tục trợ giúp mà kiểm tra việc đọc và những kết quả có thể xảy ra cho byte đầu tiên
này. Hàm thêm vào “add-on function” có nhiều cách để nhận biết việc xóa sạch hộp
mail (hộp mail rỗng), vì thế nó có thể đoán chừng để gửi byte tiếp theo vào thời điểm
thích hợp.
. Đây là TransferFirst:
1. VOID TransferFirst(PDEVICE_EXTENSION pdx)
2. {
3. pdx->busy = TRUE;
4. ULONG mbef = READ_PORT_ULONG((PULONG) (pdx-
>portbase + MBEF));
5. if (!(mbef & MBEF_IN1_0))
6. return;
7.
8. *pdx->buffer = READ_PORT_UCHAR(pdx->portbase + IMB1);
9. ++pdx->buffer;
10. ++pdx->numxfer;
11. if (—pdx->nbytes == 0)
12. {
13. pdx->busy = FALSE;
14. PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
15. Irp->IoStatus.Status = STATUS_SUCCESS;
16. Irp->IoStatus.Information = pdx->numxfer;
17. IoRequestDpc(pdx->DeviceObject, NULL, pdx);
18. }
}
S5933 có một thanh ghi rỗng/đầy cho hộp thư (empty/full register) (MBEF) các bit của
chúng chỉ ra trạng thái hiện hành của mỗi byte của mỗi thanh ghi hộp mail. ở đây chúng
ta kiểm tra liệu rằng byte thanh ghi chúng ta sử dụng cho đầu (giá trị thanh ghi hộp mail
từ 1, về 0) hiện giờ không được đọc. Nếu như vậy, chúng ta sẽ đọc nó, điều này quả
thật làm rỗng bộ đếm truyền. chúng ta đã có một thủ tục con rồi (DpcForIsr) thủ tục
này biết phải làm gì với một yêu cầu đầy đủ, vì thế chúng ta yêu cầu một DPC nếu như
byte đầu tiên thỏa mãn được yêu cầu. (Gọi lại rằng chúng ta đang thực thi tại DIRQL
dưới sự bảo vệ của một khóa quay ngắt bởi vì chúng ta đã viện dẫn như môt thủ tục
SynchCritSection vì thế chúng ta ko thể chỉ hoàn thành IRP ngay bây giờ )
253/369
Sử dụng ngắt (Handling the Interrupt )
Trong thao tác thông thường với PCI42, các ngắt S5933 khi một byte dữ liệu mới đến
một hộp thư 1. ISR sau đây sẽ giành được điều khiển:
BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject,
PDEVICE_EXTENSION pdx)
{
ULONG intcsr =
READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));
if (!(intcsr & INTCSR_INTERRUPT_PENDING))
return FALSE;
BOOLEAN dpc = FALSE;
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
if (pdx->busy)
{
if (Irp->Cancel)
status = STATUS_CANCELLED;
else
status = AreRequestsBeingAborted(&pdx->dqReadWrite);
if (!NT_SUCCESS(status))
dpc = TRUE, pdx->nbytes = 0;
254/369
}while (intcsr & INTCSR_INTERRUPT_PENDING)
{
if (intcsr & INTCSR_IMBI)
{
if (pdx->nbytes && pdx->busy)
{
*pdx->buffer = READ_PORT_UCHAR(pdx->portbase + IMB1);
++pdx->buffer;
++pdx->numxfer;
if (!--pdx->nbytes)
{
Irp->IoStatus.Information = pdx->numxfer;
dpc = TRUE;
status = STATUS_SUCCESS;
}
}
}
WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR), intcsr);
255/369
intcsr = READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));
}
if (dpc)
{
pdx->busy = FALSE;
Irp->IoStatus.Status = status;
IoRequestDpc(pdx->DeviceObject, NULL, NULL);
}
return TRUE;
}
1. Nhiệm vụ đầu tiên của chúng ta là khám phá xem liệu rằng thiết bị riêng của
chúng ta bây giờ đang cố gắng ngắt. chúng ta đọc S5933’s INTCSR và kiểm tra
một bit (INTCSR_INTERRUPT_PENDING) mà tóm tắt tất cả các lý do,
nguyên nhân treo của các ngắt. Nếu như bit này bị xóa, chúng ta sẽ trả lại ngay
lập tức. Lý do chúng ta lựa chọn sử dụng con trỏ mở rộng thiết bị như một đối
số ngữ cảnh routine—back này khi tôi gọi IoConnectInterrupt—bây giờ nên bị
xóa : chúng ta cần truy cập ngay lập tức tới cấu trúc này để có được địa chỉ
cổng cơ sở.
2. Khi chúng ta sử dụng một DEVQUEUE, chúng ta dựa vào, tin tưởng vào đối
tượng hàng đợi để giữ dấu vết của IRP hiện hành. Ngắt này có thể chúng ta
không mong đợi bởi vì ở thời điểm hiện tại đó chúng ta không đang phục vụ
bất kỳ IRp nào. Trong trường hợp đó, chúng ta vẫn phải xóa ngắt nhưng không
nên làm làm bất kỳ điều gì khác.
3. Ngoài ra vẫn có thể với một sự kiện Plug and Play hoặc sự kiện nguồn đã xảy
ra mà sẽ tạo ra bất kỳ IRP mới nào bị loại bỏ bởi thủ tục gửi thông điệp. Hàm
AreRequestsBeingAborted của DEVQUEUE’s nói cho chúng ta rằng chúng ta
có thể abort (kết thúc sớm )yêu cầu hiện hành ngay bây giờ. Việc kết thúc sớm
256/369
một yêu cầu đang hoạt động là một điều hợp lý để làm với một thiết bị chẳng
hạn như “số thu thập” byte từng byte này (proceeds byte by byte) . Tương tự
như vậy, một ý tưởng hay để kiểm tra liệu rằng IRP có bị dừng lại hay không
nếu như nó chiếm quá nhiều thời gian để kết thúc IRP. Nếu các thiết bị ngắt của
bạn chỉ khi được thực hiện với một việc truyền tải dài, bạn có thể bỏ đi bước
kiểm tra này ra khỏi ISR của mình.
4. Bây giờ chúng ta bắt tay vào một vòng lặp mà sẽ kết thúc khi tất cả các ngắt
thiết bị hiện hành đã được xóa. Ở cuối vòng lặp, chúng ta sẽ đọc lại INTCSR để
quyết định xem liệu rằng bất kỳ các điều kiện ngắt nào có thể phát sinh. Nếu
như vậy, chúng ta sẽ lặp lại vòng lặp . Chúng ta không bàn tới thời gian CPU ở
đây- chúng ta muốn tránh việc để các ngắt “chảy như thác nước” vào hệ thống
bởi vì việc phục vụ một ngắt là tương đối “đắt đỏ”
5. Nếu S5933 đã bị ngắt bởi vì một sự kiện mailbox , chúng ta sẽ đọc một byte dữ
liệu mới từ mailbox vào trong một bộ đệm I/O cho IRP hiện hành. Nếu bạn tìm
kiếm một thanh ghi MBEF ngay sau khi đọc, bạn sẽ thấy rằng việc đọc xóa bit
tương ứng của thanh ghi mailbox từ 1 về 0 (inbound mailbox register 1, byte
0). Chú ý rằng chúng ta không cần thiết kiểm tra MBEF để quyết định xem liệu
rằng byte của chúng ta thực thế có thay đổi hay không bởi vì chúng ta đã lập
trình cho thiết bị để chỉ ngắt vào lúc có một thay đổi tới byte đơn.
6. Việc ghi INTCSR với nội dung trước đó của nó có ảnh hưởng tới việc xóa bit
ngắt thứ 6 R/WC, không thay đổi một vài bit chỉ đọc (read-only bits), và bảo
toàn cài đặt gốc của tất cả các bit điều khiển chỉ đọc
7. Ở đây chúng ta đọc INTCSR để quyết định xem liệu rằng các điều kiện ngắt
thêm vào đó có xuất hiện hay không. nếu có chúng ta sẽ lặp lại vòng lặp để
phục vụ cho chúng.
8. Giống như chúng ta đã tiến hành trên các đoạn code trước, chúng ta thiết lập
biến BOOLEANdpc trở thành TRUE nếu một DPC bây giờ thích hợp để hoàn
thành IRP hiện hành.
DPC thường lệ cho PCI42 như sau::
VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT fdo, PIRP junk,
PDEVICE_EXTENSION pdx)
{
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
StartNextPacket(&pdx->dqReadWrite, fdo);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
257/369
Testing PCI42
Nếu như bạn muốn kiểm tra PCI42 trong thao tác, bạn cần phải làm một vài việc. Đầu
tiên là tìm kiếm và cài đặt một “board” phát triển S5933DK1 bao gồm card giao diện
thêm vào ISA (ISA add-in interface card). Sử dụng Add Hardware wizard để cài đặt
trình điều khiển S5933DK1.SYS và trình điều khiển PCI42.SYS. ( Tôi phát hiện rằngI
Windows 98 đồng nhất thiết lập board phát triển như việc sẽ không chạy sound cord và
tôi đã phải gỡ bỏ nó đi trong Device Manager trước khi tô có thể tiến hành cài đặt PCI42
như trình điều khiển cho nó. Nhưng Windows XP thì làm việc bình thường)
Sau đó chạy cả hai chương trình ADDONSIM và TEST chúng thuộc cây thư mục PCI42
trong nội dung sách hướng dẫn. ADDONSIM ghi một giá trị dữ liệu tới một mailbox
thông qua giao diện ISA. TEST đọc một byte dữ liệu từ PCI42. Việc quyết dịnh giá trị
của byte dữ liệu là bài tập dành cho các bạn.
258/369
Truy nhập bộ nhớ trực tiếp (Direct Memory Access )
Windows XP hỗ trợ việc truy cập trực tiếp bộ nhớ thong qua một mô hình máy tính trừu
tượng được mô tả ở hình 7.6. Trong hình này, máy tính được coi như là có một tập hợp
sơ đồ các thanh ghi cái mà được chuyển đổi giữa địa chỉ vật lý của CPU và địa chỉ bus.
Mỗi sơ đồ địa chỉ thanh ghi này lưu giữ địa chỉ của một trang vật lý nào đó. Phần cứng
truy cập bộ nhớ để đọc hay ghi bằng cách chỉ ra địa chỉ bus hay địa chỉ logic. Sơ đồ các
thanh ghi này thực hiện cùng một vai trò khi tiếp nhận bảng trang cho phần mềm bằng
cách cho phép phần cứng sử dụng các giá trị số khác nhau cho các địa chỉ của nó hơn là
để cho CPU hiểu.
Hình 7-6. Mô hình máy tính trừu tượng của bộ chuyển MDA.
Một số CUP như Alpha chẳng hạn thì có sơ đồ thanh ghi phần cứng thực tế. Một trong
các bước khởi đầu cho bộ chuyển DMA dự trữ một số thanh ghi cho quá trình sử dụng
của bạn- tôi sẽ thảo luận vấn đề này trong phần sơ đồ chuyển. Một số loại CPU khác,
như x86 chẳng hạn thì không có sơ đồ thanh ghi, bạn viết trình điều khiển của bạn tương
tự như họ làm. Sơ đồ chuyển thực hiện từng bước có thể dảo ngược từ dưới lên của
các vùng đệm bộ nhớ vật lý, cái mà phụ thuộc vào hệ thống. Trong một số trường hợp
thì hoạt động của DMA sẽ được xử lý sử dụng vùng đệm đảo ngược. Rõ rang là một
số người đã copy dữ liệu đến hoặc từ vùng đệm DMA trước hoặc sau khi dịch. Trong
trường hợp cụ thể, ví dụ như là khi chúng ta lien hệ một đường bus của thiết bị chủ, cái
mà là noi tụ họp của các cáp- các giai đoạn của sơ đồ chuyển có thể không làm gì trong
cấu trúc mà không có sơ đồ thanh ghi.
259/369
Nhân của Windows XP sử dụng một cấu trúc dữ liệu được biết đến như là đối tượng điều
hợp để mô tả các đặc tính DMA của thiết bị và để điều khiển truy cập đến các nguồn
được chia sẻ, như là hệ thống các kênh DMA và sơ đồ các thanh ghi. Bạn sẽ lấy một
con trỏ trỏ tới đối tượng điều hợp bởi lời gọi IOGetDmAAdapter trong suốt quá trình
StartDevice của bạn. Đối tượng điều hợp sẽ có một con trỏ để trỏ tới một cấu trúc được
gọi là DmaOperations cái mà khi nó bật thì chứa các con trỏ để trỏ tới các hàm mà bạn
muốn gọi. Hãy xem bảng 7.4. Các hàm này chỉ ra vị trí đích của hàm (ví dụ như ..) cái
mà bạn phải sử dụng phiên bản trước của Windown NT. Thực tế, tên đích này có trong
các Macro các mà được khai báo trong hàm DmaOperations.
Table 7-4. DmaOperations Function Pointers for
DMA Helper Routines
DmaOperations Function Pointer Description
PutDmaAdapter Destroys adapter object
AllocateCommonBuffer Allocates a common buffer
FreeCommonBuffer Releases a common buffer
AllocateAdapterChannel Reserves adapter and mapregisters
FlushAdapterBuffers Flushes intermediate data buffersafter transfer
FreeAdapterChannel Releases adapter object and mapregisters
FreeMapRegisters Releases map registers only
MapTransfer Programs one stage of a transfer
GetDmaAlignment Gets address alignment requiredfor adapter
ReadDmaCounter Determines residual count
GetScatterGatherList Reserves adapter and constructsscatter/gather list
PutScatterGatherList Releases scatter/gather list
Chiến lược chuyển đổi (Transfer Strategies):
Cách bạn thựchiện chuyển đổi DMa phụ thuộc vào nmột số nhân tố sau:
260/369
1. Nếu thiết bị của bạn có Bus-Mastering capability, tất nhiên là nó cần có điện để
truy cập vào bộ nhớ chính nếu như bạn yêu cầu nó một số chức năng có bản.
như là nơi bắt đầu, bao nhiêu đơn vị dữ liệu được chuyển, bạn đang thực hiện
việc vào hay ra dữ liệu và nhiều điều khác. Bạn sẽ phải hội ý với những người
thiết kế ra phần cứng của bạn để lọc ra được các chi tiết này hoặc là bạn sẽ phải
làm việc với bảng hướng dẫn để biết được bạn cần phải làm gì với các mức
phần cứng này.
2. Một thiết bị với khả năng tập hợp/trải ra có thể chuyển các khối lớn dữ liệu đến
hoặc đi các vùng không cấu hình của bộ nhớ vật lý. Sử dụng scatter/gather là
một lợi thể của phần mềmbởi vì nó giới hạn yêu cầu về với các khối dữ liệu lớn
của các trang Frame cấu hình. CÁc trang này có thể đơn giản là bị khoá khi mà
chúng được tìm thấy trong bộ nhớ vật lý và thiết bị có thể bị mô tả bởi chúng.
3. Nếu thiết bị của bạn không có Bus chủ, bạn sẽ sử dụng hệ thống điều khiển
DMa trên bo mạch chủ của máy tính. Kiểu của DMA đó đôi khi được gọi là
DMA nô lệ (Slave). Hệ thống điều khiển DMA lien kết với các bus ISA có một
số giới hạn về bộ nhớ nào nó được truy cập và độ rộng của một bộ chuyển nó
có thể thực hiện mà không có chương trình định trước. Trình điều khiển này cí
dụ như là IESA thiếu các giới hạn này. Ít nhất là trong Windows XP, bạn sẽ
không cần phải biết kiểu bus phần cứng cảu bạn cắm vào bởi vì hệ thống có thể
lấy ra các hạn chế này một cách tự động.
4. Thông thường, hệ thống DMA bao gồm chương trình sơ đồ các thanh ghi phần
cứng hoặc bản copy dữ liệu trước hay sau của hệ thống. nếu thiết bịcủa bạn cần
đọc hay ghi dữ liệu lien tục, bạn không cần phải thực hiện các bước này với
mỗi yêu cầu vào.ra, nó có thể làm chậm đi quá trình được chấp nhận trong
trường hợp cụ thể rất nhiều. Vì vậy bạn có thể chỉ định cái nào được biết như là
vùng đệm chung, nơi mà các thiết bị và các trình điều khiển của bạn có thể
đông thời truy cập tại nhiều thời điểm.
Tuy nhiên trong thực tế nhiều chi tiết này sẽ bị phụ thuộc khác nhau vào cách mà 4 tác
nhân này ảnh hưởng lẫn nhau, các bước mà bạn thực hiện sẽ có những đặc tính chung.
Hình 7.7 đã minh hoạc qua tổ chức của một chuyển đổi. Bạn bắt đầu chuyển đổi từ công
việc StartIo bằng cách yêu cầu quyền sở hữu của chính đối tượng điều hợp.Quyền sở
hữu này chỉ có giá trị khi bạn chia sẻ một kênh DMA hệ thống với một thiết bị khác,
nhưng mà mô hình DMA của Windows XP yêu cầu bạn cần phải thực hiện các bước
này. Khi trình quản lý vào/ra có thể cung cấp cho bạn quyền này, nó sẽ chỉ định cho bạn
một số sơ đồ các thanh ghi cho quá trình sử dụng tạm thời cảu bạnvà gọi lại hành động
điều khiển bộ điều hợp bạn cung cấp. TRong hành động điều khiển bộ điều hợp của bạn,
bạn thực hiện một sơ đồ chuyển đổi từng bước đế sắp xếp phạm vi chuyển đổi đầu tiên
(cũng có thể là chỉ có một). Một số phạm vi có thể cần thiết nếu khả năng sơ đồ các
thanh ghi là không thể. Thiết bị của bạn phải có thể cản trở và điều khiển những điều có
thể xáy ra giữa các phạm vi.
261/369
Figure 7-7. Flow of ownership during DMA.
Một thủ tục điều khiển bộ điều hợp của bạn có thể khởi tạo sơ đồ các thanh ghi cho
phạm vi đầu tiên, bạn báo hiệu cho thiết bị của mình băt đầu hoạt động. Thiết bị của bạn
sẽ thúc đẩy một ngắt khi mà quá trình chuyển đổi ban đầu được hoàn tất. VÀ sau đó thì
bạn sẽ liệt kê được một DPC. Thủ tục của DPC sẽ khởi đầu một phạm vi chuyển đổi
khácnếu cần thiết hoặc nếu không thì nó sẽ hoàn thành yêu cầu.
Đôi khi theo cách này, bạn sẽ giải phóng sơ đồ các thanh ghi và đối tượng điều hợp.
Sự tính toán thời gian của hai sự kiện này là một trong số các chi tiết khác cái mà phục
thuộc vào các tác nhân tôi đã nêu ra ở đầu của phần này.
Thực hiện chuyển đổi DM (Performing DMA Transfers):
Bây giờ tôi sẽ đi vào chi tiết về những cái máy cơ học cái mà vẫn được gọi là một gói cơ
sở DMA chuyển đổi, ở khía cạnh nào đó bạn chuyển đổi một số dữ liệu riêng biệt bằng
cách sử dụng vùng đệm dữ liệu cái mà đi cùng với gói yêu cầu vào/ra. Hãy bắt đầu một
cách đơn giản và giả sử rằng bạn đối diện với một trường hợp rất chung ngày nay: Thiết
bị của bạn là một bus PCI master nhưng không có khả năng phân giải/ tụ tập
Khi bạn tạo ra đối tượng thiết bị của mình, để bắt đầu bạn thong thường sẽ biểu thị
điều bạn muốn để sử dụng phương thức truy cập trực tiếp vùng đệm dữ liệu bằng
việc thiết lập cờ DO_DIRECT_IO. Bạn chọn phương thức trực tiếp bởi vì bạn thậm
chí sẽ phải thong qua địa chỉ của một kí hiệu bộ nhớ lập danh sách khi một trong
số các tham số hàm MapTransfẻ bạn sẽ gọi. Sự lựa chọn này sẽ đưa ra một số vấn
đề đáng quan tâm về sự sắp hang của vùng đệm. Trừ phi mà ứng dụng sử dụng cờ
FILE_FLAG_NO_BUFFERING trong lời gọi của nó tới hàm CreatFile, trình quản lý
262/369
vào ra sẽ không bắt buộc yêu cầu xếp hang của đối tượng thiết bị trên các vùng đệm
dữ liệu ở chế độ người dùng (nó không bắt buộc các yêu cầu cho nhân ở chế độ lời gọi
trong ). Nếu Hal hoặc thiết bị của bạn yêu cầu các vùng đệm DMA để bắt đầu trong
một ranh giới cụ thể, vì thế bạn có thể copy từ duới lên một phần nhỏ của dữ liệu người
dùng tới hang chính xác bên trong vùng đệm để có được một hang đợi yêu cầu- hoặc là
điều đó hoặc nguyên nhân để làm sai và yêu cầu điều đó có một vùng đệm không sắp
hàng.
TRong hàm StartDevice, bạn tạo một đối tượng điềuhợp bằng cách sử dụng đoạn code
như sau:
DEVICE_DESCRIPTION dd;
RtlZeroMemory(&dd, sizeof(dd));
dd.Version = DEVICE_DESCRIPTION_VERSION;
dd.Master = TRUE;
dd.InterfaceType = InterfaceTypeUndefined;
dd.MaximumLength = MAXTRANSFER;
dd.Dma32BitAddresses = TRUE;
pdx->AdapterObject = IoGetDmaAdapter(pdx->Pdo, &dd,
&pdx->nMapRegisters);
Câu lệnh cuối cùng trong đoạn code này là quan trọng bậc nhất. IoGetDmaAdapter sẽ
giao tiếp với bus điều khiển hoặc Hal để tạo ra một đối tượng điều hợp, cái mà địa chỉ
của nó được trả về cho bạn. Tham số đầu tiên (pdx→Pdo) định nghĩa đối tượng thiết bị
vật lý cho đối tượng của bạn. Tham số thứ hai chỉ tới cấu trúc DEVICE_DESCRIPTION
cái mà bạn khởi tạo để mô tả một DMA tiêu biếu cho thiết bị của bạn. Tham số cuối
cùng chỉ ra nơi mà hệ thống nên lưu giữ số lượng lớn nhất các số của sơ đồ các thanh
ghi mà bạn sẽ được phép cố gắng lưu trữ để dự trữ trong suốt quá trình chuyển đơn. Bạn
sẽ cần phải lưu ý rằng tôi đã lưu trữ hai trường trong thiết bị mở rộng để nhận lấy hai dữ
liệu ra từ hàm này.
Để khởi đầu cho hoạt động vào/ra, thủ tục StartIo đầu tiên cần phải dự trữ một đối
tượng bằng lời gọi thủ tục AllocateAdapterChannel của đối tượng. Một trong các tham
số truyền tới hàm AllocateAdapterChannel là địa chỉ của một thủ tục điều khiển điều
hợp, cái mà trình quản lý vào ra sẽ gọi khi sự lưu trữ xong xuôi. Đây là đoạn code mẫu
mà bạn sử dụng để chuẩn bị và thực hiện lời gọi tới AllocateAdapterChannel:
263/369
typedef struct _DEVICE_EXTENSION {
PADAPTER_OBJECT AdapterObject; // device's adapter object
ULONG nMapRegisters; // max # map registers
ULONG nMapRegistersAllocated; // # allocated for this xfer
ULONG numxfer; // # bytes transferred so far
ULONG xfer; // # bytes to transfer during this stage
ULONG nbytes; // # bytes remaining to transfer
PVOID vaddr; // virtual address for current stage
PVOID regbase; // map register base for this stage
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
VOID StartIo(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx =
(PDEVICE_EXTENSION) fdo->DeviceExtension;
PMDL mdl = Irp->MdlAddress;
pdx->numxfer = 0;
pdx->xfer = pdx->nbytes = MmGetMdlByteCount(mdl);
264/369
pdx->vaddr = MmGetMdlVirtualAddress(mdl);
ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr,
pdx->nbytes);
if (nregs > pdx->nMapRegisters)
{
nregs = pdx->nMapRegisters;
pdx->xfer = nregs * PAGE_SIZE - MmGetMdlByteOffset(mdl);
}
pdx->nMapRegistersAllocated = nregs;
NTSTATUS status = (*pdx->AdapterObject->DmaOperations
->AllocateAdapterChannel)(pdx->AdapterObject, fdo, nregs,
(PDRIVER_CONTROL) AdapterControl, pdx);
if (!NT_SUCCESS(status))
{
CompleteRequest(Irp, status, 0);
StartNextPacket(&pdx->dqReadWrite, fdo);
}
}
1. thiết bị mở rộng của bạn cần phải lưu trữ một số trường có lien quan đến các
chuyển đổi DMA. Phần chú thích chỉ ra các trường hợp cho các trường này.
2. Đây là một số câu lệnh khởi tạo cho các trường trong thiết bị mở rộng cho
phạm vi đầu tiên của sự chuyển đổi.
265/369
3. Ở đây chúng ta tính toán số lượng các sơ đồ các thanh ghi chúng ta yêu cầu hệ
thống lưu trữ cho chúng ta trong suốt quá trình chuyển đổi này. Chúng ta bắt
đầu bằng việc tính toán số lượng được yêu cầu cho toàn bộ sự chuyển đổi này.
Macro ADDRESS_AND_SIZE_TO_SPAN_PAGES có thể đưa vào một bản
kê khai vùng đệm có thể kéo dài qua ranh giới của một trang. Tuy nhiên con số
mà chúng ta đi ngược từ dưới lên với khả năng có thể vượt qua số lớn nhất mà
chúng ta được cho phépbởi lời gọi thong thường tới hàm IoGetDmAAdapter.
TRong trường hợp này chúng ta cần thực hiện một chuyển đổi trong nhiều
phạm vi. Do vậy chúng ta đảo ngược tỉlệ của trang đầu tiên để chỉ sử dụng con
số chấp nhận được của sơ đồ các thanh ghi. Chúng ta cũng cấn phải nhớ có bao
nhiêu sơ đồ các thanh ghi mà chúng ta đang cho phép để chúng ta có thể huỷ
chính xác con số này về sau.
4. TRong lời gọi tới hàm AllocateAdapterChannel, chúng ta chỉ ra địa chỉ của đ ối
tượng Adapter, địa chỉ của đối tượng thiết bị của chúng ta, số lượng đã được
tính toán của sơ đồ các thanh ghi, và địa chỉ của thủ tục điều khiển điều hợ của
chúng ta. Tham số cuối cùng pdx là tham số ngữ cảnh cho thủ tục điều khiển
điều hợp của chúng ta.
Thông thường thì một vài thiết bị có thể chia sẻ chung một đối tượng điều hợp đơn, đối
tượng điều hợp đang chia sẻ trong cuộc sống thực tế chỉ khi bạn tin tưởng vào hệ thống
điều khiển DMA. Các thiết bị Bus-Master tận dụng các đối tượng điều hợp thuộc quyền
sở hữu.Nhưng bởi vì bạn không cần phải biết về hệ thống các thiết bị khi bạn tạo ra các
đối tượng điều hợp, nên bạn cũng không cần phải đưa ra bất cứ chứng minhnào vềnó.
Khi đó thong thường đối tượng điều hợp của chúng ta sẽ rất bận rộn khi bạn gọi tới
AllocateAdapterChannel, và yêu cầu của bạn vì thế có thể bị đặt vào trong một hang đợi
cho tới khi đối tượng điềuhợp trở nên có thể xử lý (rảnh rỗi). Tất cả khoảng trễ này xảy
ra bên trong của AllocateAdapterChannel cái mà sẽ gọi thủ tục điều khiển điều hợp của
bạn khi đối tượng điều hợp và tất cả sơ đồ các thanh ghi mà bạn yêu cầu rảng rỗi.
Nhiệm vụ tiếp theo của bạn là thực hiện những gì mà thiết bị phụ thuộc của bạn yêu cầu
để nói cho thiết bị của bạn biết về địa chỉ vật lý để bắt đầu hoạt động trong phần cứng
của bạn.
IO_ALLOCATION_ACTION AdapterControl(PDEVICE_OBJECT fdo,
PIRP junk, PVOID regbase, PDEVICE_EXTENSION pdx)
{
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
266/369
PMDL mdl = Irp->MdlAddress;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
BOOLEAN isread = stack->MajorFunction == IRP_MJ_READ;
pdx->regbase = regbase;
KeFlushIoBuffers(mdl, isread, TRUE);
PHYSICAL_ADDRESS address =
(*pdx->AdapterObject->DmaOperations->MapTransfer)
(pdx->AdapterObject, mdl, regbase, pdx->vaddr, pdx->xfer,
!isread);
return DeallocateObjectKeepRegisters;
}
1. Tham số thứ 2 cái mà tôi đặt tên là junk to AdapterControl is whatever(bất cứ
khi nào) was in the CurrentIrp field of the device object khi mà bạn gọi hàm
AllocateAdapterChannel. Khi bạn sử dụng DEVQUEUE cho hang đợi IRP, bạn
cần phải yêu cầu đối tượng DEVQUEUE cái mà là IRP hiện tại. Nếu bạn sử
dụng hang đợi các thủ tục của Microsoft IoStartPacker & IoStartNextPacket để
quảnlý hang đợi của bạn thì junk sẽ đúng là một IRP. Trong trường hợp này tôi
sẽ gọi tên Irp để thay thế.
267/369
2. Có một số điều khác biệt giữa đoạn code điều khiển hoạt động vào và ra sử
dụng DMA, vì thế nó rất thuận tiện cho việc điều khiển cả hai hoạ
Các file đính kèm theo tài liệu này:
- bai_giang_lap_trinh_he_thong.pdf