Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память

Дата публикации 27 ноя 2003

Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память — Архив WASM.RU



В предыдущем примере SharedSection, где мы совместно использовали раздел, драйвер был жестко привязан к адресному контексту конкретного процесса, т.к. виртуальный адрес имевшийся у драйвера указывал в адресное пространство этого процесса. Метод, который мы используем в этом примере лишен этого недостатка. Для драйверов этот метод намного более естественен.


9.1 Исходный текст драйвера SharingMemory

Сначала разберёмся с тем, что происходит в драйвере.

Код (Text):
  1.  
  2.  
  3.  ;@echo off
  4.  ;goto make
  5.  
  6.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  7.  ;
  8.  ; SharingMemory - Пример того, как драйвер может передать в пользовательский процесс
  9.  ;                 используемую им область памяти
  10.  ;
  11.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  12.  
  13.  .386
  14.  .model flat, stdcall
  15.  option casemap:none
  16.  
  17.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  18.  ;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
  19.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  20.  
  21.  include \masm32\include\w2k\ntstatus.inc
  22.  include \masm32\include\w2k\ntddk.inc
  23.  
  24.  include \masm32\include\w2k\ntoskrnl.inc
  25.  include \masm32\include\w2k\hal.inc
  26.  
  27.  includelib \masm32\lib\w2k\ntoskrnl.lib
  28.  includelib \masm32\lib\w2k\hal.lib
  29.  
  30.  include \masm32\Macros\Strings.mac
  31.  
  32.  include ..\common.inc
  33.  include seh0.inc
  34.  
  35.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  36.  ;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                                
  37.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  38.  
  39.  .const
  40.  CCOUNTED_UNICODE_STRING "\\Device\\SharingMemory", g_usDeviceName, 4
  41.  CCOUNTED_UNICODE_STRING "\\DosDevices\\SharingMemory", g_usSymbolicLinkName, 4
  42.  
  43.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  44.  ;                    Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е
  45.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  46.  
  47.  .data?
  48.  g_pSharedMemory     PVOID   ?
  49.  g_pMdl              PVOID   ?
  50.  g_pUserAddress      PVOID   ?
  51.  
  52.  g_fTimerStarted     BOOL    ?
  53.  
  54.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  55.  ;                                           К О Д                                                  
  56.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  57.  
  58.  .code
  59.  
  60.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  61.  ;                                        UpdateTime                                                
  62.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  63.  
  64.  UpdateTime proc
  65.  
  66.  local SysTime:LARGE_INTEGER
  67.  
  68.      invoke KeQuerySystemTime, addr SysTime
  69.      invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory
  70.  
  71.      ret
  72.  
  73.  UpdateTime endp
  74.  
  75.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  76.  ;                                       TimerRoutine                                                
  77.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  78.  
  79.  TimerRoutine proc pDeviceObject:PDEVICE_OBJECT, pContext:PVOID
  80.  
  81.      invoke UpdateTime
  82.  
  83.      ret
  84.  
  85.  TimerRoutine endp
  86.  
  87.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  88.  ;                                          Cleanup                                                  
  89.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  90.  
  91.  Cleanup proc pDeviceObject:PDEVICE_OBJECT
  92.  
  93.      .if g_fTimerStarted
  94.          invoke IoStopTimer, pDeviceObject
  95.          invoke DbgPrint, $CTA0("SharingMemory: Timer stopped\n")
  96.      .endif
  97.  
  98.      .if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
  99.          invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
  100.          invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X unmapped\n"), g_pUserAddress
  101.          and g_pUserAddress, NULL
  102.      .endif
  103.  
  104.      .if g_pMdl != NULL
  105.          invoke IoFreeMdl, g_pMdl
  106.          invoke DbgPrint, $CTA0("SharingMemory: MDL at address %08X freed\n"), g_pMdl
  107.          and g_pMdl, NULL
  108.      .endif
  109.  
  110.      .if g_pSharedMemory != NULL
  111.          invoke ExFreePool, g_pSharedMemory
  112.          invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X released\n"), g_pSharedMemory
  113.          and g_pSharedMemory, NULL
  114.      .endif
  115.  
  116.      ret
  117.  
  118.  Cleanup endp
  119.  
  120.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  121.  ;                                     DispatchCleanup                                              
  122.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  123.  
  124.  DispatchCleanup proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
  125.  
  126.      invoke DbgPrint, $CTA0("\nSharingMemory: Entering DispatchCleanup\n")
  127.  
  128.      invoke Cleanup, pDeviceObject
  129.  
  130.      mov eax, pIrp
  131.      mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
  132.      and (_IRP PTR [eax]).IoStatus.Information, 0
  133.  
  134.      fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT
  135.  
  136.      invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchCleanup\n")
  137.  
  138.      mov eax, STATUS_SUCCESS
  139.      ret
  140.  
  141.  DispatchCleanup endp
  142.  
  143.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  144.  ;                                   DispatchCreateClose                                            
  145.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  146.  
  147.  DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
  148.  
  149.      mov eax, pIrp
  150.      mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
  151.      and (_IRP PTR [eax]).IoStatus.Information, 0
  152.  
  153.      fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT
  154.  
  155.      mov eax, STATUS_SUCCESS
  156.      ret
  157.  
  158.  DispatchCreateClose endp
  159.  
  160.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  161.  ;                                     DispatchControl                                              
  162.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  163.  
  164.  DispatchControl proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
  165.  
  166.  local status:NTSTATUS
  167.  local dwContext:DWORD
  168.  
  169.      invoke DbgPrint, $CTA0("\nSharingMemory: Entering DispatchControl\n")
  170.  
  171.      mov esi, pIrp
  172.      assume esi:ptr _IRP
  173.  
  174.      mov [esi].IoStatus.Status, STATUS_UNSUCCESSFUL
  175.      and [esi].IoStatus.Information, 0
  176.  
  177.      IoGetCurrentIrpStackLocation esi
  178.      mov edi, eax
  179.      assume edi:ptr IO_STACK_LOCATION
  180.  
  181.      .if [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_GIVE_ME_YOUR_MEMORY
  182.          .if [edi].Parameters.DeviceIoControl.OutputBufferLength >= sizeof PVOID
  183.  
  184.              invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
  185.              .if eax != NULL
  186.                  mov g_pSharedMemory, eax
  187.  
  188.                  invoke DbgPrint, \
  189.                  $CTA0("SharingMemory: %X bytes of nonpaged memory allocated at address %08X\n"), \
  190.                  PAGE_SIZE, g_pSharedMemory
  191.  
  192.                  invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
  193.                  .if eax != NULL
  194.                      mov g_pMdl, eax
  195.  
  196.                      invoke DbgPrint, \
  197.                              $CTA0("SharingMemory: MDL allocated at address %08X\n"), g_pMdl
  198.  
  199.                      invoke MmBuildMdlForNonPagedPool, g_pMdl
  200.  
  201.                      _try
  202.  
  203.                      invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
  204.                                          NULL, FALSE, NormalPagePriority
  205.                      .if eax != NULL
  206.  
  207.                          mov g_pUserAddress, eax
  208.  
  209.                          invoke DbgPrint, \
  210.                          $CTA0("SharingMemory: Memory mapped into user space at address %08X\n"), \
  211.                          g_pUserAddress
  212.  
  213.                          mov eax, [esi].AssociatedIrp.SystemBuffer
  214.                          push g_pUserAddress
  215.                          pop dword ptr [eax]
  216.  
  217.                          invoke UpdateTime
  218.  
  219.                          invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext
  220.                          .if eax == STATUS_SUCCESS
  221.  
  222.                              invoke IoStartTimer, pDeviceObject
  223.                              inc g_fTimerStarted
  224.  
  225.                              invoke DbgPrint, $CTA0("SharingMemory: Timer started\n")
  226.  
  227.                              mov [esi].IoStatus.Information, sizeof PVOID
  228.                              mov [esi].IoStatus.Status, STATUS_SUCCESS
  229.  
  230.                          .endif
  231.                      .endif
  232.  
  233.                      _finally
  234.  
  235.                  .endif
  236.              .endif
  237.  
  238.          .else
  239.              mov [esi].IoStatus.Status, STATUS_BUFFER_TOO_SMALL
  240.          .endif
  241.      .else
  242.          mov [esi].IoStatus.Status, STATUS_INVALID_DEVICE_REQUEST
  243.      .endif
  244.  
  245.      assume edi:nothing
  246.  
  247.      .if [esi].IoStatus.Status != STATUS_SUCCESS
  248.  
  249.          invoke DbgPrint, $CTA0("SharingMemory: Something went wrong\:\n")
  250.  
  251.          invoke Cleanup, pDeviceObject
  252.  
  253.      .endif
  254.  
  255.      push [esi].IoStatus.Status
  256.  
  257.      assume esi:nothing
  258.  
  259.      fastcall IofCompleteRequest, esi, IO_NO_INCREMENT
  260.  
  261.      invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchControl\n")
  262.  
  263.      pop eax
  264.      ret
  265.  
  266.  DispatchControl endp
  267.  
  268.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  269.  ;                                       DriverUnload                                                
  270.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  271.  
  272.  DriverUnload proc pDriverObject:PDRIVER_OBJECT
  273.  
  274.      invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
  275.  
  276.      mov eax, pDriverObject
  277.      invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject
  278.  
  279.      ret
  280.  
  281.  DriverUnload endp
  282.  
  283.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  284.  ;               В Ы Г Р У Ж А Е М Ы Й   П Р И   Н Е О Б Х О Д И М О С Т И   К О Д                  
  285.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  286.  
  287.  .code INIT
  288.  
  289.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  290.  ;                                       DriverEntry                                                
  291.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  292.  
  293.  DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
  294.  
  295.  local status:NTSTATUS
  296.  local pDeviceObject:PDEVICE_OBJECT
  297.  
  298.      mov status, STATUS_DEVICE_CONFIGURATION_ERROR
  299.  
  300.      and g_pSharedMemory, NULL
  301.      and g_pMdl, NULL
  302.      and g_pUserAddress, NULL
  303.      and g_fTimerStarted, FALSE
  304.  
  305.      invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, FILE_DEVICE_UNKNOWN, \
  306.                                      0, TRUE, addr pDeviceObject
  307.      .if eax == STATUS_SUCCESS
  308.          invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
  309.          .if eax == STATUS_SUCCESS
  310.              mov eax, pDriverObject
  311.              assume eax:ptr DRIVER_OBJECT
  312.              mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],          offset DispatchCreateClose
  313.              mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)],         offset DispatchCleanup
  314.              mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],           offset DispatchCreateClose
  315.              mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)],  offset DispatchControl
  316.              mov [eax].DriverUnload,                                         offset DriverUnload
  317.              assume eax:nothing
  318.              mov status, STATUS_SUCCESS
  319.          .else
  320.              invoke IoDeleteDevice, pDeviceObject
  321.          .endif
  322.      .endif
  323.  
  324.      mov eax, status
  325.      ret
  326.  
  327.  DriverEntry endp
  328.  
  329.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  330.  ;                                                                                                  
  331.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  332.  
  333.  end DriverEntry
  334.  
  335.  :make
  336.  
  337.  set drv=SharingMemory
  338.  
  339.  \masm32\bin\ml /nologo /c /coff %drv%.bat
  340.  \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj
  341.  
  342.  del %drv%.obj
  343.  move %drv%.sys ..
  344.  
  345.  echo.
  346.  pause
  347.  
  348.  



9.1.1 Процедура DriverEntry

Код (Text):
  1.  
  2.  
  3.              mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],          offset DispatchCreateClose
  4.              mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)],         offset DispatchCleanup
  5.              mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],           offset DispatchCreateClose
  6.              mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)],  offset DispatchControl
  7.  
  8.  

Помимо обычных запросов IRP_MJ_CREATE, IRP_MJ_CLOSE и IRP_MJ_DEVICE_CONTROL будем обрабатывать также IRP_MJ_CLEANUP. Когда код режима пользователя вызывает CloseHandle, драйверу сначала посылается IRP_MJ_CLEANUP, сигнализируя о том, что описатель устройства драйвера сейчас будет закрыт. После того как описатель будет фактически закрыт, драйвер получает IRP_MJ_CLOSE. В данном примере желательно освободить ресурсы как можно раньше. Поэтому и потребовалась обработка IRP_MJ_CLEANUP.



9.1.2 Процедура DispatchControl

Код (Text):
  1.  
  2.  
  3.              invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
  4.              .if eax != NULL
  5.                  mov g_pSharedMemory, eax
  6.  
  7.  

Получив управляющий код IOCTL_GIVE_ME_YOUR_MEMORY, выделяем одну страницу неподкачиваемой памяти. Зачем нам нужна именно неподкачиваемая память и почему именно одна страница будет ясно по ходу дела.

Драйвер должен будет отобразить эту память в адресное пространство пользовательского процесса, в контексте которого получен запрос, т.е. в адресное пространство нашей программы управления драйвером.

ExAllocatePool вернёт адрес из системного диапазона, т.е. обращаться по нему драйвер может независимо от текущего контекста. Теперь необходимо отобразить эту память в адресное пространство того процесса, с которым мы хотим эту память совместно использовать. Наш драйвер одноуровневый, поэтому при обработке IRP_MJ_DEVICE_CONTROL мы находимся в адресном контексте нашей программы управления. Прежде чем мы отобразим в её адресное пространство выделенную страницу памяти необходимо сформировать MDL (Memory Descriptor List. Перевод этого термина на русский язык я не знаю).



9.1.3 Memory Descriptor List

MDL представляется одноименной структурой и содержит описание физических страниц региона памяти.

Код (Text):
  1.  
  2.  
  3.  MDL STRUCT
  4.      Next            PVOID       ?
  5.      _Size           SWORD       ?
  6.      MdlFlags        SWORD       ?
  7.      Process         PVOID       ?
  8.      MappedSystemVa  PVOID       ?
  9.      StartVa         PVOID       ?
  10.      ByteCount       DWORD       ?
  11.      ByteOffset      DWORD       ?
  12.  MDL ENDS
  13.  PMDL typedef PTR MDL
  14.  
  15.  

Точнее говоря, структура MDL это заголовок. Сразу за заголовком идет массив двойных слов Pages, каждое из которых представляет собой номер физической страницы (page frame number, PFN). Хотя регион памяти описываемый MDL является непрерывным в виртуальном пространстве адресов, занимаемые им физические страницы могут располагаться в физической памяти в произвольном порядке. Именно поэтому и нужен массив Pages, содержащий список всех физических страниц занимаемых регионом памяти. Также он требуется для организации прямого доступа к памяти (Direct Memory Access, DMA). В нашем случае страница всего одна. Т.о. MDL содержит всю необходимую диспетчеру памяти информацию.

Код (Text):
  1.  
  2.  
  3.                  invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
  4.                  .if eax != NULL
  5.                      mov g_pMdl, eax
  6.  
  7.  

Первые два параметра функции IoAllocateMdl определяют виртуальный адрес и размер блока памяти, для которого надо сформировать MDL. Если MDL не ассоциируется с IRP (именно так и есть в нашем случае), то третий параметр равен FALSE. Четвертый параметр определяет, нужно ли уменьшить квоту процесса, и применим только для драйверов находящихся на самом верхнем уровне в цепочке драйверов или для одноуровневых драйверов (именно таковым наш драйвер и является). Каждый процесс получает от системы квоты на ресурсы. Когда процесс выделяет себе ресурс, квота уменьшается. Если квота кончается, то и соответствующий ресурс больше не выделяется. Мы не хотим уменьшать квоту процесса на выделяемую память, поэтому четвертый параметр будет равен FALSE. Последний параметр определяет необязательный указатель на IRP, с которым ассоциируется MDL. Например, при прямом вводе-выводе диспетчер ввода-вывода создает MDL для пользовательского буфера и передает его адрес в IRP.MdlAddress. Мы формируем MDL не для операции ввода-вывода, поэтому никакого IRP у нас нет, и последний параметр будет равен NULL.

Т.о. функция IoAllocateMdl выделяет память для MDL и инициализирует его заголовок.

Код (Text):
  1.  
  2.  
  3.                      invoke MmBuildMdlForNonPagedPool, g_pMdl
  4.  
  5.  

MmBuildMdlForNonPagedPool заполняет массив номеров физических страниц и обновляет некоторые поля заголовка MDL.

Код (Text):
  1.  
  2.  
  3.                      _try
  4.  
  5.  

Если параметр AccessMode функции MmMapLockedPagesSpecifyCache, которую мы сейчас будем вызывать, равен UserMode и её вызов окончится неудачей, система вбрасывает исключение (это явно указано в DDK), которое мы сможем обработать, поставив SEH-фрейм.

Код (Text):
  1.  
  2.  
  3.                      invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
  4.                                          NULL, FALSE, NormalPagePriority
  5.  
  6.  

Отображаем память, описываемую MDL в адресное пространство нашей программы управления.

Первый параметр указывает на MDL, описывающий регион памяти подлежащий отображению. Второй параметр определяет, можно ли будет обращаться к этой памяти из режима пользователя. Третий определяет политику кэширования этой памяти процессором. Если четвертый параметр равен NULL, то система сама выберет виртуальный адрес в пользовательском пространстве. Пятый параметр определяет, появится ли BSOD, если вдруг система не сможет удовлетворить запрос, но только если второй параметр равен KernelMode. Тем не менее, передаем в этом параметре FALSE, т.к. не хотим рушить систему ни при каких обстоятельствах. Последний параметр определяет насколько важно, чтобы вызов MmMapLockedPagesSpecifyCache прошёл успешно.

В Windows NT4 функция MmMapLockedPagesSpecifyCache не реализована. Вместо неё используйте MmMapLockedPages таким образом:

Код (Text):
  1.  
  2.  
  3.  invoke MmMapLockedPages, g_pMdl, UserMode
  4.  
  5.  

MmMapLockedPages имеется и в последующих выпусках Windows и является простой оболочкой вокруг MmMapLockedPagesSpecifyCache, но управлять последними четырьмя параметрами возможности нет.

С помощью MDL в пользовательское адресное пространство можно отобразить только заблокированные, т.е. находящиеся в неподкачиваемой памяти страницы (во всяком случае, я не знаю, как это сделать для подкичиваемой памяти). Это первая причина, по которой нам потребовалась неподкачиваемая память.

Отобразить меньше одной страницы нельзя. Поэтому нам нужна целая страница, хотя реально будет использовано всего несколько байт.

Код (Text):
  1.  
  2.  
  3.                      .if eax != NULL
  4.                          mov g_pUserAddress, eax
  5.  
  6.                          mov eax, [esi].AssociatedIrp.SystemBuffer
  7.                          push g_pUserAddress
  8.                          pop dword ptr [eax]
  9.  
  10.  

MmMapLockedPagesSpecifyCache вернет адрес из пользовательского диапазона, по которому она отобразила нашу страницу. Передаем его в программу управления. Т.о. с этого момента страница памяти становится совместно используемой, причём драйвер сможет обращаться к ней независимо от текущего адресного контекста, а пользовательский процесс будет обращаться к ней по доступному ему адресу.

Код (Text):
  1.  
  2.  
  3.                          invoke UpdateTime
  4.  
  5.  

Для наглядности процедура UpdateTime будет помещать на разделяемую страницу текущее системное время.

Код (Text):
  1.  
  2.  
  3.  UpdateTime proc
  4.  
  5.  local SysTime:LARGE_INTEGER
  6.  
  7.      invoke KeQuerySystemTime, addr SysTime
  8.      invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory
  9.  
  10.      ret
  11.  
  12.  UpdateTime endp
  13.  
  14.  

KeQuerySystemTime сообщает системное время по Гринвичу. ExSystemTimeToLocalTime корректирует его с учётом часового пояса.

Код (Text):
  1.  
  2.  
  3.                          invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext
  4.  
  5.  

Инициализируем таймер, который будет ассоциирован с управляемым драйвером объектом "устройство". Для этого в структуре DEVICE_OBJECT имеется поле Timer, являющееся указателем на структуру IO_TIMER. Первый параметр функции IoInitializeTimer определяет, с каким объектом "устройство" надо связать таймер. Второй - указатель на процедуру вызываемую системой при срабатывании таймера. Процедура TimerRoutine будет обновлять системное время на разделяемой странице, вызывая UpdateTime. TimerRoutine выполняется при IRQL = DISPATCH_LEVEL (об этом явно написано в DDK). Это вторая причина, по которой нам требуется неподкачиваемая память. Последний параметр функции IoInitializeTimer - указатель на произвольные данные. Этот указатель будет передан в TimerRoutine. Нам никакие дополнительные данные не нужны, поэтому dwContext у нас просто фиктивная переменная.

Код (Text):
  1.  
  2.  
  3.                          .if eax == STATUS_SUCCESS
  4.  
  5.                              invoke IoStartTimer, pDeviceObject
  6.                              inc g_fTimerStarted
  7.  
  8.  

Запускаем таймер. Теперь процедура TimerRoutine будет вызываться примерно раз в секунду. Изменить этот интервал нельзя.

Код (Text):
  1.  
  2.  
  3.      .if [esi].IoStatus.Status != STATUS_SUCCESS
  4.          invoke Cleanup, pDeviceObject
  5.      .endif
  6.  
  7.  

Если на одном из предыдущих этапов возникли проблемы, проводим очистку ресурсов.



9.1.4 Процедура Cleanup

Код (Text):
  1.  
  2.  
  3.  Cleanup proc pDeviceObject:PDEVICE_OBJECT
  4.  
  5.      .if g_fTimerStarted
  6.          invoke IoStopTimer, pDeviceObject
  7.      .endif
  8.  
  9.      .if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
  10.          invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
  11.          and g_pUserAddress, NULL
  12.      .endif
  13.  
  14.      .if g_pMdl != NULL
  15.          invoke IoFreeMdl, g_pMdl
  16.          and g_pMdl, NULL
  17.      .endif
  18.  
  19.      .if g_pSharedMemory != NULL
  20.          invoke ExFreePool, g_pSharedMemory
  21.          and g_pSharedMemory, NULL
  22.      .endif
  23.  
  24.      ret
  25.  
  26.  Cleanup endp
  27.  
  28.  

Здесь должно быть все понятно без объяснений. Единственная тонкость - отображение памяти в пользовательское пространство и обратная операция, выполняемая с помощью функции MmUnmapLockedPages, должны проходить в адресном контексте определенного процесса, что вполне естественно.



9.2 Исходный текст программы управления драйвером SharingMemory

Код (Text):
  1.  
  2.  
  3.  ;@echo off
  4.  ;goto make
  5.  
  6.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  7.  ;
  8.  ; Клиент драйвера SharingMemory
  9.  ;
  10.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  11.  
  12.  .386
  13.  .model flat, stdcall
  14.  option casemap:none
  15.  
  16.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  17.  ;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
  18.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  19.  
  20.  include \masm32\include\windows.inc
  21.  
  22.  include \masm32\include\kernel32.inc
  23.  include \masm32\include\user32.inc
  24.  include \masm32\include\advapi32.inc
  25.  
  26.  includelib \masm32\lib\kernel32.lib
  27.  includelib \masm32\lib\user32.lib
  28.  includelib \masm32\lib\advapi32.lib
  29.  
  30.  include \masm32\include\winioctl.inc
  31.  
  32.  include \masm32\Macros\Strings.mac
  33.  
  34.  include ..\common.inc
  35.  
  36.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  37.  ;                                      E Q U A T E S                                                
  38.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  39.  
  40.  IDD_MAIN            equ 1000
  41.  IDC_TIME            equ 1001
  42.  IDI_ICON            equ 1002
  43.  
  44.  TIMER_ID            equ     100
  45.  
  46.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  47.  ;                    Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е
  48.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  49.  
  50.  .data?
  51.  g_hDevice           HANDLE      ?
  52.  g_hInstance         HINSTANCE   ?
  53.  g_hDlg              HWND        ?
  54.  g_pSharedMemory     LPVOID      ?
  55.  
  56.  g_hSCManager        HANDLE      ?
  57.  g_hService          HANDLE      ?
  58.  
  59.  
  60.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  61.  ;                                           К О Д                                                  
  62.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  63.  
  64.  .code
  65.  
  66.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  67.  ;                             MyUnhandledExceptionFilter                                            
  68.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  69.  
  70.  MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS
  71.  
  72.  local _ss:SERVICE_STATUS
  73.  
  74.      invoke KillTimer, g_hDlg, TIMER_ID
  75.      invoke CloseHandle, g_hDevice
  76.      invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
  77.      invoke DeleteService, g_hService
  78.      invoke CloseServiceHandle, g_hService
  79.      invoke CloseServiceHandle, g_hSCManager
  80.  
  81.      mov eax, EXCEPTION_EXECUTE_HANDLER
  82.      ret
  83.  
  84.  MyUnhandledExceptionFilter endp
  85.  
  86.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  87.  ;                                              UpdateTime                                          
  88.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  89.  
  90.  UpdateTime proc
  91.  
  92.  local stime:SYSTEMTIME
  93.  local buffer[64]:CHAR
  94.  
  95.      .if g_pSharedMemory != NULL
  96.          invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
  97.          movzx eax, stime.wHour
  98.          movzx ecx, stime.wMinute
  99.          movzx edx, stime.wSecond
  100.  
  101.          invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx
  102.  
  103.          invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
  104.      .endif
  105.  
  106.      ret
  107.  
  108.  UpdateTime endp
  109.  
  110.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  111.  ;                               D I A L O G     P R O C E D U R E                                  
  112.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  113.  
  114.  DlgProc proc uses esi edi hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  115.  
  116.      mov eax, uMsg
  117.      .if eax == WM_TIMER
  118.  
  119.          invoke UpdateTime
  120.  
  121.      .elseif eax == WM_INITDIALOG
  122.  
  123.          push hDlg
  124.          pop g_hDlg
  125.  
  126.          invoke LoadIcon, g_hInstance, IDI_ICON
  127.          invoke SendMessage, hDlg, WM_SETICON, ICON_BIG, eax
  128.  
  129.          invoke SetWindowText, hDlg, $CTA0("Kernel Timer")
  130.  
  131.          invoke UpdateTime
  132.  
  133.          invoke SetTimer, hDlg, TIMER_ID, 1000, NULL
  134.  
  135.      .elseif eax == WM_COMMAND
  136.  
  137.          mov eax, wParam
  138.          .if ax == IDCANCEL
  139.              invoke EndDialog, hDlg, 0
  140.          .endif
  141.  
  142.      .elseif eax == WM_DESTROY
  143.  
  144.          invoke KillTimer, hDlg, TIMER_ID
  145.  
  146.      .else
  147.  
  148.          xor eax, eax
  149.          ret
  150.    
  151.      .endif
  152.  
  153.      xor eax, eax
  154.      inc eax
  155.      ret
  156.    
  157.  DlgProc endp
  158.  
  159.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  160.  ;                                       start                                                      
  161.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  162.  
  163.  start proc uses esi edi
  164.  
  165.  local acModulePath[MAX_PATH]:CHAR
  166.  local _ss:SERVICE_STATUS
  167.  local dwBytesReturned:DWORD
  168.  
  169.      and g_pSharedMemory, NULL
  170.  
  171.      invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
  172.  
  173.      invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
  174.      .if eax != NULL
  175.          mov g_hSCManager, eax
  176.  
  177.          push eax
  178.          invoke GetFullPathName, $CTA0("SharingMemory.sys"), sizeof acModulePath, addr acModulePath, esp
  179.          pop eax
  180.  
  181.          invoke CreateService, g_hSCManager, $CTA0("SharingMemory"), $CTA0("Another way how to share memory"), \
  182.              SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
  183.              SERVICE_ERROR_IGNORE, addr acModulePath, NULL, NULL, NULL, NULL, NULL
  184.  
  185.          .if eax != NULL
  186.              mov g_hService, eax
  187.  
  188.              invoke StartService, g_hService, 0, NULL
  189.              .if eax != 0
  190.  
  191.                  invoke CreateFile, $CTA0("\\\\.\\SharingMemory"), GENERIC_READ, \
  192.                                  0, NULL, OPEN_EXISTING, 0, NULL
  193.  
  194.                  .if eax != INVALID_HANDLE_VALUE
  195.                      mov g_hDevice, eax
  196.  
  197.                      ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  198.  
  199.                      invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, \
  200.                                  addr g_pSharedMemory, sizeof g_pSharedMemory, \
  201.                                  addr dwBytesReturned, NULL
  202.  
  203.                      .if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )
  204.  
  205.                          invoke GetModuleHandle, NULL
  206.                          mov g_hInstance, eax
  207.                          invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0
  208.  
  209.                      .else
  210.                          invoke MessageBox, NULL, $CTA0("Can't send control code to device."), \
  211.                                                      NULL, MB_OK + MB_ICONSTOP
  212.                      .endif
  213.  
  214.                      ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  215.  
  216.                      invoke CloseHandle, g_hDevice
  217.                  .else
  218.                      invoke MessageBox, NULL, $CTA0("Device is not present."), NULL, MB_ICONSTOP
  219.                  .endif
  220.                  invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
  221.              .else
  222.                  invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_OK + MB_ICONSTOP
  223.              .endif
  224.              invoke DeleteService, g_hService
  225.              invoke CloseServiceHandle, g_hService
  226.          .else
  227.              invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_OK + MB_ICONSTOP
  228.          .endif
  229.          invoke CloseServiceHandle, g_hSCManager
  230.      .else
  231.          invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), NULL, MB_OK + MB_ICONSTOP
  232.      .endif
  233.  
  234.      invoke ExitProcess, 0
  235.  
  236.  start endp
  237.  
  238.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  239.  ;                                                                                                  
  240.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  241.  
  242.  end start
  243.  
  244.  :make
  245.  
  246.  set exe=SharingMemory
  247.  
  248.  if exist ..\%scp%.exe del ..\%scp%.exe
  249.  
  250.  if exist rsrc.obj goto final
  251.      \masm32\bin\rc /v rsrc.rc
  252.      \masm32\bin\cvtres /machine:ix86 rsrc.res
  253.      if errorlevel 0 goto final
  254.          pause
  255.          exit
  256.  
  257.  :final
  258.  if exist rsrc.res del rsrc.res
  259.  
  260.  \masm32\bin\ml /nologo /c /coff %exe%.bat
  261.  \masm32\bin\link /nologo /subsystem:windows %exe%.obj rsrc.obj
  262.  
  263.  del %exe%.obj
  264.  move %exe%.exe ..
  265.  if exist %exe%.exe del %exe%.exe
  266.  
  267.  echo.
  268.  pause
  269.  
  270.  

Каждый поток в пользовательских процессах помещается системой в SEH-фрейм, для того чтобы обрабатывать любые исключения в этом потоке возникающие. Если поток не устанавливает дополнительных обработчиков исключений, то при возникновении исключения системный обработчик вызывает знаменитый диалог и подключает отладчик, при наличии такового. Вызовом функции SetUnhandledExceptionFilter можно заменить системный обработчик исключений на свой собственный.

Код (Text):
  1.  
  2.  
  3.  
  4.      invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
  5.  
  6.  

Т.о. фактически не устанавливая никаких SEH-фреймов, мы будем иметь возможность провести необходимую в данном случае очистку ресурсов при возникновении любого исключения. На обработчик MyUnhandledExceptionFilter посмотрим чуть позже.

Код (Text):
  1.  
  2.  
  3.                      invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, \
  4.                                  addr g_pSharedMemory, sizeof g_pSharedMemory, \
  5.                                  addr dwBytesReturned, NULL
  6.  
  7.  

Если драйвер удачно запустился, передаем ему управляющий код IOCTL_GIVE_ME_YOUR_MEMORY. В ответ драйвер вернет нам в переменной g_pSharedMemory адрес, по которому он отобразил разделяемый буфер памяти. Его размер нас в данном случае не интересует, т.к. он заведомо больше наших потребностей. В первых 8 байтах содержится текущее время, которое обновляется драйвером каждую секунду.

Код (Text):
  1.  
  2.  
  3.                      .if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )
  4.  
  5.                          invoke GetModuleHandle, NULL
  6.                          mov g_hInstance, eax
  7.                          invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0
  8.  
  9.  

Если все прошло удачно, запускаем диалог. Дальше всё элементарно.

Код (Text):
  1.  
  2.  
  3.      .elseif eax == WM_INITDIALOG
  4.          . . .
  5.          invoke UpdateTime
  6.          invoke SetTimer, hDlg, TIMER_ID, 1000, NULL
  7.  
  8.  

При обработке сообщения WM_INITDIALOG вызываем процедуру UpdateTime. Это нужно для того, чтобы сразу после появления диалога отобразить текущее время. Затем запускаем таймер, который будет срабатывать один раз в секунду.

Код (Text):
  1.  
  2.  
  3.      .if eax == WM_TIMER
  4.          invoke UpdateTime
  5.  
  6.  

При обработке сообщения WM_TIMER также вызываем UpdateTime для обновления времени.

Код (Text):
  1.  
  2.  
  3.  UpdateTime proc
  4.  
  5.  local stime:SYSTEMTIME
  6.  local buffer[64]:CHAR
  7.  
  8.      .if g_pSharedMemory != NULL
  9.          invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
  10.          movzx eax, stime.wHour
  11.          movzx ecx, stime.wMinute
  12.          movzx edx, stime.wSecond
  13.  
  14.          invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx
  15.  
  16.          invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
  17.      .endif
  18.  
  19.      ret
  20.  
  21.  UpdateTime endp
  22.  
  23.  

Задачей процедуры UpdateTime является форматирование и вывод текущего времени в общепринятом формате Часы:Минуты:Секунды.

Рис. 9-1. Результат работы программы SharingMemory.exe

Т.о. раз в секунду драйвер помещает на разделяемую страницу текущее время, обращаясь при этом по виртуальному адресу в системном адресном пространстве, а программа управления, также раз в секунду, забирает эту информацию, обращаясь при этом по виртуальному адресу в пользовательском адресном пространстве. Но физически разделяется одна страница памяти. Т.о. часы "тикают" каждую секунду. Кстати, функция KeQuerySystemTime получает текущее время также обращаясь к разделяемой между ядром и режимом пользователя странице, которая в режиме ядра спроецирована по адресу 0FFDF0000h, а в режиме пользователя по адресу 7FFE0000h (пользовательская функция GetSystemTime читает те же самые байты что и функция ядра KeQuerySystemTime) и описывается структурой KUSER_SHARED_DATA (см. ntddk.inc). Даже по названию этой структуры видно, что она разделяется ядром и режимом пользователя.

Код (Text):
  1.  
  2.  
  3.  MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS
  4.    
  5.  local _ss:SERVICE_STATUS
  6.  
  7.      invoke KillTimer, g_hDlg, TIMER_ID
  8.      invoke CloseHandle, g_hDevice
  9.      invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
  10.      invoke DeleteService, g_hService
  11.      invoke CloseServiceHandle, g_hService
  12.      invoke CloseServiceHandle, g_hSCManager
  13.  
  14.      mov eax, EXCEPTION_EXECUTE_HANDLER
  15.      ret
  16.  
  17.  MyUnhandledExceptionFilter endp
  18.  
  19.  

Если в любой точке программы управления возникнет исключение, система вызовет наш обработчик MyUnhandledExceptionFilter. Всё что мы можем сделать - это освободить все выделенные нами ресурсы. Самое главное закрыть описатель устройства. Тогда драйвер получит IRP_MJ_CLEANUP, а затем IRP_MJ_CLOSE и также проведет очистку, самой главной из которых является отключение (unmap) региона памяти от пользовательского адресного пространства. На самом деле, можно даже обойтись без обработчика исключений. Если программа управления и рухнет, система сама закроет все открытые описатели, в том числе и описатель устройства. Мы отключаем нашу разделяемую страницу при обработке IRP_MJ_CLEANUP просто из желания сделать очистку ресурсов как можно раньше. В данном случае можно сделать это и при обрабтке IRP_MJ_CLOSE. В любом случае MmUnmapLockedPages должна быть вызвана до того, как пользовательский процесс прекратит своё существование.

В отличие от предыдущего примера с разделяемым разделом, здесь у нас уже два потока обращающихся к разделяемому ресурсу памяти. Т.е. необходимо подумать о синхронизации. Читающий поток работает в режиме пользователя, а значит всегда выполняется при IRQL = PASSIVE_LEVEL. Пишущий поток принадлежит системному процессу и выполняет процедуру TimerRoutine, адрес которой мы определили в вызове IoInitializeTimer. Процедура TimerRoutine вызывается системой при IRQL = DISPATCH_LEVEL (об этом недвусмысленно написано в DDK) и выполняется потоком процесса простоя (idle process), во всяком случае, в моих экспериментах работал именно этот поток. Т.к. его приоритет ниже, чем приоритет пользовательского потока, то он не может прервать программу управления драйвером в момент считывания данных с разделяемой страницы. Т.к. при IRQL = DISPATCH_LEVEL планирования потоков не происходит, пользовательский поток не может прервать системный в момент записи текущего времени на разделяемую страницу. Т.о. на однопроцессорной машине никаких проблем с синхронизацией возникнуть не должно. На многопроцессорной машине возможна одновременная работа этих потоков. Поэтому в подобных ситуациях требуется подумать о синхронизации. В данном случае мы не предпринимаем никаких усилий в этом направлении, т.к. это тема для одной из следующих статей. При стечении самых неблагоприятных обстоятельств в одну из секунд в диалоге отобразится неверное значение времени. В данном случае, больше нам это ничем не грозит.

Исходный код драйвера в архиве.

© Four-F

0 1.525
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532