Chapter 14: Using Assembly Language for System Programming in Windows

image from book  Download CD Content

In this chapter, we will focus on issues related to the optimization of system programming tasks in Windows operating systems. System programming is a very complicated area that requires a strong knowledge of how the operating system works. Improvement of application quality is possible only when a programmer thoroughly understands those features of the operating system that are related to software optimization.

System programming deals with tasks such as managing the file system, memory and processes, inter-process communication, networking, and other tasks that use Application Programming Interface, specifically , WIN API functions of 32-bit operating systems of Windows family. In this chapter, we will demonstrate a few aspects of optimizing such tasks with a low level language such as the assembler. Traditional advantages of the assembler such as compactness and speed of code execution can be useful when implementing system-programming tasks as well.

Some issues related to optimizing tasks with the assembler (such as multithreading, timers, and system services) are discussed in other chapters. This chapter will focus on optimization of file operations and memory management. These operations are decisive for performance of most applications, so increasing the effectiveness of their execution is important.

We will begin with file operations. Copying, moving, searching, and deleting files and directories can be done with both C++ .NET library functions and the operating system s WIN API functions. The performance of execution depends heavily on the algorithm used in the file operations. Now, we will look at file copying.

The performance of this file operation strongly depends on the size of the read/write buffer, the way in which the data are arranged in the memory, and the number of bytes being copied or moved.

When copying one file to another, data conversion is often required. Software implementation of such a conversion significantly affects application performance. The inline assembler allows you to implement effective data conversion with minimum loss of performance. Also, the assembler allows you to write specific algorithms for data processing and conversion that would be difficult to implement in C++ with only library functions.

Here is an example. Suppose you want to substitute spaces in a file with plus characters and save a copy of the file with another name . Suppose also that the source file is a text file named readfile , and the destination file is named writefile . Character substitution will be done with an assembly block. The source code of the console application is shown in Listing 14.1.

Listing 14.1: Copying files with character substitution
image from book
 // COPY_F_C_ASM.cpp : Defines the entry point for the console  // application  #include "stdafx.h"  #include <stdio.h>  int _tmain(int argc, _TCHAR* argv[])  {    FILE *fin, *fout;    char buf [256];    int bRead, bWritten;    if ( (fin = fopen( "d: \readfile", "r" )) == NULL)    {     printf ( "The file 'readf' was not opened\n");     exit(1);    }  // Open for write    if ( (fout = fopen( "d: \writefile", "w+" )) == NULL)     {        printf ( "The file 'writefile' was not opened\n" );        exit (1);   }   while ((bRead = fread(buf, sizeof (char) , sizeof (buf), fin)) > 0)   {     _asm {          mov  ECX, bRead          lea  ESI, buf          mov  AL, ' '   next_ch:          cmp  BYTE PTR [ESI], AL          je   repl          inc  ESI          dec  ECX          jnz  next_ch          jmp  ex      repl:          mov  [ESI], '+'          inc  ESI          dec  ECX          jnz  next_ch       ex:        };     bWritten = fwrite (buf, sizeof(char), bRead, fout);   };     fclose (fin);     fclose (fout);     return 0;  } 
image from book
 

The assembly block does the conversion. The address of the memory buffer that stores the read data is loaded in the ESI register. The ECX register contains the number of bytes to process. The character being substituted, i.e., a space, is in the AL register. Each iteration compares a character in the memory to the character in the AL register. If they are equal, a plus character is written to the memory address, at which the space is stored, and the program jumps to the next iteration. The address of the item in the memory buffer is incremented, and the character counter is decremented:

 cmp  BYTE PTR [ESI], AL  je   repl  inc  ESI  dec  ECX  jnz  next_ch 

When the characters are not equal, the program jumps to the next iteration and simultaneously increments the address in the ESI register.

After conversion, the contents of the buffer are saved in a new file with the fout handle:

 bWritten = fwrite(buf, sizeof(char), bRead, fout) 

Using the assembler, you can create rather complicated algorithms for processing data in files, and the possibilities are unlimited.

None of these applications can do without memory operations. C++ includes the malloc function and the new operator. In most cases, programmers get along with these tools. However, some tasks require more flexible control over memory. In such cases, a WIN API function VirtualAlloc is very useful.

This function is widely used and has several important advantages in comparison with the malloc library function.

Unlike malloc , the virtualAlloc function allows you to allocate a block of memory aligned on the page boundary and assign access attributes to it (such as read, read/write, executable, etc.). This makes it possible to process data appropriately and with maximum speed. Also, the VirtualAlloc function can reserve memory without physically allocating it, which decreases the load on the operating system as a whole.

The next example relates to the use of the VirtualAlloc memory allocation function when copying. Copying is done in an assembly block, in which string primitive commands with the rep repetition prefix are used. This makes it possible to perform the operation with high speed. The source code of the application that uses the advantages of both the VirtualAlloc function and string primitive assembly commands is shown in Listing 14.2.

Listing 14.2: Copying characters with the virtualAlloc function
image from book
 // VA_EXAMPLE.cpp : Defines the entry point for the console application  #include "stdafx.h"  #include <windows.h>  #include <time.h>  int _tmain(int argc, _TCHAR* argv[])  {    int* src = NULL;    int* dst = NULL;    printf(" VirtualAlloc copying with ASM EXAMPLE\n\n");    srand( (unsigned)time (NULL) );    src = (int*)VirtualAlloc(NULL, 10, MEM_COMMIT, PAGE_READWRITE);    int* bsrc = src;    dst = (int*)VirtualAlloc (NULL, 10, MEM_COMMIT, PAGE_READWRITE);    printf ("\nsrc :");    for (int cnt = 0; cnt < 10; cnt++)     {        *src = rand();        printf ("%d ", *src);        src++;     }   _asm {         mov  ESI, bsrc         mov  EDI, dst         mov  ECX, 10         cld         rep  movsd      }   printf("\n\ndst : ") ;   for (int cnt = 0; cnt < 10; cnt++)     {        printf ("%d ", *dst) ;        dst++;     }    VirtualFree(bsrc, 0, MEM_RELEASE);    VirtualFree(dst, 0, MEM_RELEASE);    getchar ();    return 0;  } 
image from book
 

Note the following statement:

 int* bsrc = src 

It is necessary for correctly setting the address of the array in the bsrc pointer.

The memory for a ten-element array of random numbers src is allocated with the command:

 src = (int*) VirtualAlloc (NULL, 10, MEM_COMMIT, PAGE_READWRITE) 

The memory for a destination array dst is allocated in a similar fashion:

 dst = (int*)VirtualAlloc(NULL, 10, MEM_COMMIT, PAGE_READWRITE) 

High-speed copying is done with the following assembly commands:

 mov  ESI, bsrc  mov  EDI, dst  mov  ECX, 10  cld  rep  movsd 

When you do not need the allocated memory any longer, you should free it. This is done with the functions:

 VirtualFree(bsrc, 0, MEM_RELEASE)  VirtualFree(dst, 0, MEM_RELEASE) 

The window of the application is shown in Fig. 14.1.

image from book
Fig. 14.1: Window of an application that copies integers with the VirtualAlloc function

Windows operating systems support another very useful technology for working with files. File mapping to the memory is done. This is very convenient for processing files with several processes simultaneously, and this technology is widely used. The virtual memory manager of the operating system allows an application to work with a file as if it is loaded in the computer s memory. To work with a file mapped to the memory, proceed as follows :

  1. Open the file with the CreateFile function.

  2. Pass the file handle to the CreateFileMapping WIN API function.

  3. Get a pointer to the memory buffer containing the file with the MapViewOfFile function. This is a common pointer, i.e., you can perform on it any operations valid for pointers.

  4. After you complete the work with the file, call the UnmapViewOf File function.

  5. Delete the handle to the file-mapping object and close the file handle with the CloseHandle function.

The next example demonstrates the use of file mapping to memory. Depending on which option is selected (one or zero), alphanumeric characters in the testmap text file are converted to the upper or lower case. Like in the previous listing, the conversion is done with the inline assembler, which provides good performance. The source code of the application is shown in Listing 14.3.

Listing 14.3: Converting a file mapped to the memory with the assembler
image from book
 // FILE_MAPPING_EXAMPLE.cpp : Defines the entry point for the console  // application  #include "stdafx.h"  #include <windows.h>  int _tmain(int argc, _TCHAR* argv[])  {   HANDLE fin;   HANDLE map_fin;   char* mapBase = NULL;   int fSize;   int choice = 0;   printf("   USING FILE MAPPING WITH ASM OPTIMIZING\n\n");   printf("Enter 1 - convert to upper, 0 - convert to lower:");   scanf("%d", &choice);   fin = CreateFile("d:\testmap", GENERIC_WRITEGENERIC_READ,                   0, NULL, OPEN_EXISTING,                   0, NULL);   if (fin == INVALID_HANDLE_VALUE)    {     printf("Cannot open file\n");     exit(1);    }  fSize = GetFileSize(fin, NULL);   map_fin = CreateFileMapping (fin, NULL,                                PAGE_READWRITE, 0,                                0, NULL);   if (!map_fin)   {    printf ("Cannot open mapping\n");    getchar();    exit(2);    }       mapBase = (char*)MapViewOf File (map_fin,                                    FILE_MAP_WRITE,                                    0, 0, 0);   if (!mapBase)    {     printf ("Cannot get the map pointer\n") ;     getchar();     exit(1);    }   char* dmapBase = mapBase;   switch (choice) {           case 1:            _asm {                  mov  ECX, fSize                  mov  EDI, dmapBase        next_char:                  mov  AL, BYTE PTR [EDI]                  cmp  AL, 96                  jg   high_check                  jmp  next      high_check:                  cmp  AL, 122                  jg   next                  sub  AL, 32                  mov  BYTE PTR [EDI], AL           next:                  add  EDI, 1                  dec  ECX                  jnz  next_char                  }           break;       case 0:            _asm {                  mov  ECX, fSize                  mov  EDI, dmapBase       next1_char:                  mov  AL, BYTE PTR [EDI]                  cmp  AL, 64                  jg   high1_check                  jmp  next1       high1_check:                  cmp  AL, 90                  jg   next                  add  AL, 32                  mov  BYTE PTR [EDI], AL            next1:                  add  EDI, 1                  dec  ECX                  jnz  next1_char                  };         break;      default:         break;     };   printf("\n      NEW CONTENT OF FILE: \n\n")   for (int cnt = 0; cnt < fSize; cnt++)        printf("%c", *mapBase++);   UnmapViewOfFile(mapBase);   CloseHandle(map_fin);   CloseHandle(fin);   getchar();   return 0;  } 
image from book
 

The window, in which characters are converted to the upper case, is shown in Fig. 14.2, and the window with the conversion to the lower case is in Fig. 14.3.

image from book
Fig. 14.2: Converting characters in a text file to the upper case
image from book
Fig. 14.3: Converting characters in a text file to the lower case

This completes the discussion of using the assembler for optimization of system programming tasks. This chapter addressed the most important system programming tasks and their optimization with the assembler.



Visual C++ Optimization with Assembly Code
Visual C++ Optimization with Assembly Code
ISBN: 193176932X
EAN: 2147483647
Year: 2003
Pages: 50
Authors: Yury Magda

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net