Tips on Optimizing the ROM Footprint of Your OS (Windows Embedded Compact 7)
When you design a Windows Embedded Compact 7 OS for a device that has limited ROM, the size of the OS footprint in ROM is particularly important. You can calculate the size of the OS and its individual modules and files by using the techniques described in the article How to Analyze OS ROM Size, which is based on Johnson Shih’s video on Controlling ROM Size.
This article, which is also based on Johnson Shih’s video, provides tips on how to reduce the OS size by using two approaches: working with modules and working with Sysgens. Because there is a trade-off between OS ROM size, RAM consumption, and performance, this article covers ways to offset impacts on RAM and performance that may result from a reduction in ROM size. This article includes information on how to:
Work with modules and files
During the build process, Make Binary Image tool (MakeImg.exe) uses a number of Binary Image Builder (.bib) Files to determine which modules and files to include in the OS image and where to place these components within ROM. The MODULES and FILES sections of the .bib files are of most interest to us. For more information on these sections, see the companion article How to Analyze OS ROM Size. During the build process, all of the .bib files are merged into the ce.bib file, which is in your flat release directory. Because all of the files that are listed under MODULES and FILES in ce.bib are pulled into nk.bin, ce.bib provides a good place for you to start your investigation on what is in ROM.
Arrange your files in ROM
It is important that you select the most appropriate section (MODULES or FILES) for your code and data because this choice affects the trade-off between ROM usage, RAM consumption, and performance. In brief, put native code executables in the MODULES section, and put non-executables such as data, resource DLLs, managed code, audio, and bitmap files in the FILES section.
Modules section
Makeimg strips off extra information from executables in the MODULES section and fixes up the modules in ROM, so put your executables in the MODULES section. (It would not make sense to put non-executables under MODULES because Makeimg can’t fix them up.) When you put your executables under the MODULES section, you improve your system because:
- Fix-up improves boot-up time. At run time when the OS boots up, the kernel doesn’t have to spend time relocating the modules.
- Fix-up saves ROM. When Makeimg strips out the record-keeping parts of the executables it reduces the size of the modules in ROM.
Files section
By default, files in the FILES section are compressed, but you can choose to leave them uncompressed. To leave a file uncompressed, give the file a U
attribute in its .bib file. You need to be aware of the advantages and disadvantages when you use compression. Consider the following:
- Compression saves ROM. Of course, when you compress a file, it takes up less space in ROM.
- Compression impacts performance. If the OS uses a file frequently, and the file is compressed, the OS has to repeatedly decompress the file at run time. You can reduce the impact on performance (decompression time) by enabling the file cache.
- Compression increases RAM consumption. Compressed files increase RAM consumption because the CPU must allocate memory to hold the decompressed data. By tuning the paging pools as described later in this article, you can balance ROM size and RAM usage by limiting memory consumption to a given size.
Note that managed code is considered non-executable because it is not native code, so Makeimg can’t fix it up. Therefore, put managed code in the FILES section. For example, .NET DLL looks like an executable, but it’s not. Put it under FILES.
Review your non-executable files
You can review your non-executable files to see if you have an opportunity to save space. Here are some tips:
- Select image files that match the resolution and color depth of your device’s display. It is a waste of ROM to include an image that is of higher quality than your device’s display can render.
- Experiment with different image formats. For instance, try using a compressed format such as .jpg or .gif instead of .bmp.
- Generate simple images programmatically. For example, for a background, perhaps you can use a solid color instead of an image file.
- Choose the appropriate bit rate for audio files. When you lower the bit rate, you generally reduce the audio quality, but some audio files may sound acceptable with lower bit rates.
- Reuse multimedia files. You may be able to reuse images and audio files that are already in ROM.
- Package your data properly. Use multiple execute-in-place (XIP) regions to package your data in terms of locale, resolution, and so on. By using XIP, the OS can run code directly from ROM rather than loading it into RAM. By using multiple XIP regions instead of a single region, you can divide the ROM image into discrete parts. For example, if your project supports different resolutions and color depths, put your resolution-related modules into separate binary image (.bin) files instead of putting them all in the same .bin file. That way, you can pick the .bin that is most appropriate for the project. For information on how to specify multiple XIP regions, see Romimage Tool.
Review your executable files
You may be able to rework some of your executables (that is, the modules that you put in the MODULES section of your .bib files) to save some space in ROM. This task can be difficult to do for stability reasons. It may involve making big changes in executables, so you have to do a lot of testing. You may want to consider the following suggestions for reworking executables:
- Separate your resources into a resource DLL. You can easily check your modules for resources by using the DUMPBIN tool. By using the command
DUMPBIN /HEADERS <your module>
, you can see if there is a resource embedded in your module. In the Dumpbin output, look at the.text
and.data
sections for code and data size, respectively. Look at the.rsrc
section for resource size. Consider moving the resources under.rsrc
into a resource DLL. When you separate this content, for example, you benefit when you use multiple-locale support; it gives you the flexibility to swap out resources for different locales. The following code snippet shows Dumpbin output with the resource size highlighted.
- Remove redundant and dead code. For example, check for functions and variables that are never used. Parameters that are never used make your code bigger and slower also. Detect dead code by manual code review, a static code analysis tool, or by running a code-coverage tool for hints.
- Avoid unnecessary floating-point arithmetic. The compiler generates additional code for floating-point operations.
- Avoid statically linked libraries. When you use a statically linked library, you pull a bunch of object code into your module. Link to DLLs instead of pulling in code statically. Check your sources files and makefile files to make sure that you link to the correct library files.
- Reorganize your code. You can put large constant data tables into separate data files instead of your binaries so that your OS can open the files at run time. Also, instead of allocating large amounts of static data in a module (data that you need to initialize), allocate memory for large read-write data at run time. If you allocate the memory at run time, only the constants need to be in the data section within ROM.
Work with Sysgens
Catalog items are associated with Sysgen variables. When you select a catalog item in Platform Builder, you increase ROM size because you are pulling in the code for the selected Sysgen and any Sysgens that it depends on. Here are some steps that you can take to minimize the number of Sysgens that you use:
- Analyze the Sysgens that are enabled in your OS. Focus on Sysgens that control your module and Sysgens that your module depends on. You can check within Platform Builder, batch files, and ceconfig.h to do this analysis. For more information, see the companion article How to Analyze OS ROM Size.
- Use only the Sysgens that you need to build your project. If your module can build without a Sysgen, then you don’t need it. For example, perhaps you wrote a reusable module that works with or without a software-based input panel (SIP). In this module, you call SipGetInfo to test whether there is a SIP present and then act accordingly. However, you know that the particular device that you are designing the OS for does not use a SIP. In this case, you do not want to include SYSGEN_SOFTKB in the OS because it will take up unnecessary space. Instead of enabling SYSGEN_SOFTKB, you could write a stub for this function, delay-load the DLL, or use GetProcAddress to see if an implementation of SipGetInfo exists, and if not, treat it as if the SIP is disabled.
- Create a BSP that can adapt to Sysgens. Have your BSP adapt to different Sysgen combinations by using Cesysgen Conditional Statements and by compiling components into separate libraries.
Use Cesysgen conditional statements. You can use Cesysgen conditional statements to build only the BSP components that your OS will use. For example, you can use conditional statements in files such as platform.bib, platform.reg, and DIRS. In the following excerpt of a DIRS file, the battery driver code is only included if the battery module is needed.
Compile separate libraries. Another (more advanced) way that you can make your BSP adapt to different Sysgen combinations is to compile your BSP so that the components are in separate libraries and only link to the libraries when necessary. For example, you can compile a touch driver into its own library. When touch is enabled in the OS design, have the linker pull in the driver. This approach will shorten BSP build time too.
You can create a build rule in %_WINCEROOT%\platform\<BSP Name>\cesysgen\makefile. How to create build rules is beyond the scope of this article, but you can look at %_WINCEROOT\public\common\cesysgen\makefile for reference. The sample BSPs that ship with Windows Embedded Compact 7 do not often use build rules, but the Device Emulator BSP that shipped with Windows Embedded CE 6.0 did use many build rules, so you can look at the makefile file of the Device Emulator BSP if you have Windows Embedded CE 6.0 installed.
Offset impacts on RAM and performance
OS ROM size, memory usage, and performance are related. When you reduce the ROM footprint, RAM consumption may increase and the speed of the system may be negatively affected. To reduce these impacts, you can enable file caching and tune the paging pool as explained in the following sections.
Enable file caching
When you use file compression, the OS needs to decompress the file before it uses it, which takes time. You can reduce the impact on performance by enabling file caching. When you use file caching, the OS decompresses the file only once and then keeps the content in RAM. To enable file caching, see Registry Settings for File Caching.
Tune the paging pool size
You can use the paging pool to limit the amount of memory that each module can consume. There are two paging pools: the loader pool, which is for read-only files such as executable code and read-only data, and the file pool, which is for read-write data such as file-backed memory-mapped files and the file cache filter. For information on how the paging pool works, see the article Paging and the Windows CE Paging Pool.
In Windows CE 5.0, the paging pool was off by default. In Windows Embedded CE 6.0 and Windows Embedded Compact 7, it is on by default. First consider whether you need to use the paging pool at all. The decision depends on whether your OS uses XIP. If your OS uses XIP, you may be able to turn the paging pool off or use a smaller paging pool size because XIP does not use RAM.
Choose the paging pool size
When the paging pool is too small, pages are frequently paged out and then paged in again. This is called thrashing. When the paging pool size is too high, you are reserving RAM that might not be used. You need to find a balance. To choose the correct pool size, be aware that the following scenarios can affect RAM usage differently:
- When the OS boots up
- When the OS runs multiple applications at the same time
- When the OS runs large applications
- When the OS runs applications that read a lot of data
After you have identified your important scenarios, you can do the following:
- Use Celog event tracking with boot zones. In the Celog output log, look for multiple faults on the same page, which indicates that the kernel is paging in and paging out too frequently. If there are a lot of faults, increase the loader pool size. If there aren’t many, it may indicate that the paging pool is too big and it is being underutilized. If so, decrease the loader pool size.
- Call IOCTL_KLIB_GET_POOL_STATE periodically to find the current sizes of the pools. The pool sizes are in the PagePoolState structure. If the sizes of the pools are at the maximum all of the time, consider increasing the sizes and then see how much the pools are being used. For example, as a test, double the size of a pool and then see how much of the pool is being used. In the
PagePoolState
structure, you can also look at theTrimCount
andCriticalCount
parameters to determine if the paging pool is too small or too large. For example, ifTrimCount
andCriticalCount
are high, then the kernel is frequently paging data out of the pool to get the memory usage down to the target size. Consider increasing the pool size.
Set the paging pool size
The default size of the loader pool and the file pool is 3 MB and 1 MB, respectively. There are two ways to set the paging pool size. You can set it during the Makeimg build stage, or you can change it at run time. Information on both methods follows.
- To set the paging pool size during Makeimg. You can set the paging pool size during the build stage by using FIXUPVAR in config.bib. In %_WINCEROOT%\platform\<BSP Name>\files\config.bib, set the target size parameters (
LoaderPoolTarget
andFilePoolTarget
) of the loader and file paging pools. You can also setPageOutLow
, which is the threshold below which pages are freed by the trimmer thread andPageOutHigh
, which is the amount of memory above which pages are not freed by the trimmer thread. The following is an example of this information in config.bib. (This excerpt shows the default values).
- To set the paging pool size at run time. To set the paging pool size at run time, you need to support IOCTL_HAL_GET_POOL_PARAMETERS. The structure that holds the paging pool size is PagePoolParameters, as shown in the following syntax block. In the
PagePoolParameters
structure, only theTarget
variable is used. The other variables are legacy code.
Summary of tips
The following is a check-list for you to review as you try to reduce OS ROM size.
- Is your module in the correct section (MODULES or FILES) of your .bib file?
- Is your module linked to the correct libraries?
- Does your module contain dead code or duplicate code?
- Does your module contain large constant data tables?
- Are your non-executable files in the correct format?
- Does your module pull in unnecessary Sysgens?
- Can your module be further componentized to adopt different Sysgen settings?
- Can you find a better design or implementation?
Keep this list handy!
Community Resources
See Also