Typed memory

Typed memory is POSIX functionality defined in the 1003.1 specification. It's part of the advanced realtime extensions, and the manifests are located in the <sys/mman.h> header file. Typed memory adds the following functions to the C library:

posix_typed_mem_open()
Open a typed memory object. This function returns a file descriptor, which you can then pass to mmap() to establish a memory mapping of the typed memory object.
posix_typed_mem_get_info()
Get information (currently the amount of available memory) about a typed memory object.

POSIX typed memory provides an interface to open memory objects (which are defined in an OS-specific fashion) and perform mapping operations on them. It's useful in providing an abstraction between BSP- or board-specific address layouts and device drivers or user code.

Implementation-defined behavior

POSIX specifies that typed memory pools (or objects) are created and defined in an implementation-specific fashion. This section describes the following for BlackBerry 10 OS:

Seeding of typed memory regions

Under BlackBerry 10 OS, typed memory objects are defined from the memory regions specified in the asinfo section of the system page. Thus, typed memory objects map directly to the address space hierarchy (asinfo segments) define by startup. The typed memory objects also inherit the properties defined in asinfo, namely the physical address (or bounds) of the memory segments. In general, the naming and properties of the asinfo entries is arbitrary and is completely under the user's control. There are, however, some mandatory entries:

memory
Physical addressability of the processor, typically 4 GB on a 32-bit CPU (more with physical addressing extensions).
ram
All of the RAM on the system. This may consist of multiple entries.
sysram
System RAM, that is, memory that has been given to the OS to manage. This may also consist of multiple entries.

Since by convention sysram is the memory that has been given to the OS, this pool is the same as that used by the OS to satisfy anonymous mmap() and malloc() requests.

You can create additional entries, but only in startup, using the as_add() function.

Naming of typed memory regions

The names of typed memory regions are derived directly from the names of the asinfo segments. The asinfo section itself describes a hierarchy, and so the naming of typed memory object is a hierarchy. Here's a sample system configuration:

Name Range (start, end)
/memory 0, 0xFFFFFFFF
/memory/ram 0, 0x1FFFFFF
/memory/ram/sysram 0x1000, 0x1FFFFFF
/memory/isa/ram/dma 0x1000, 0xFFFFFF
/memory/ram/dma 0x1000, 0x1FFFFFF

The name you pass to posix_typed_mem_open() follows the above naming convention. POSIX allows an implementation to define what happens when the name doesn't start with a leading slash (/). The resolution rules on opening are as follows:

  1. If the name starts with a leading /, an exact match is done.
  2. The name may contain intermediate / characters. These are considered as path component separators. If multiple path components are specified, they're matched from the bottom up (the opposite of the way filenames are resolved).
  3. If the name doesn't start with a leading /, a tail match is done on the pathname components specified.

Here are some examples of how posix_typed_mem_open() resolves names, using the above sample configuration:

This name: Resolves to: See:
/memory /memory Rule 1
/memory/ram /memory/ram Rule 2
/sysram Fails  
sysram /memory/ram/sysram Rule 3

Pathname space and typed memory

The typed memory name hierarchy is exported through the process manager namespace under /dev/tymem. Applications can list this hierarchy, and look at the asinfo entries in the system page to get information about the typed memory.

Unlike for shared memory objects, you can't open typed memory through the namespace interface, because posix_typed_mem_open() takes the additional parameter tflag, which is required and isn't provided in the open() API.

mmap() allocation flags and typed memory objects

The following general cases of allocations and mapping are considered for typed memory:

  • The typed memory pool is explicitly allocated from (POSIX_TYPED_MEM_ALLOCATE and POSIX_TYPED_MEM_ALLOCATE_CONTIG). This case is just like a normal MAP_SHARED of a anonymous object:

    mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON,
         NOFD, 0);
    

    The memory is allocated and not available for other allocations, but if you fork the process, the child processes can access it as well. The memory is released when the last mapping to it is removed.

    Note that like somebody doing mem_offset() and then a MAP_PHYS to gain access to previously allocated memory, somebody else could open the typed memory object with POSIX_TYPED_MEM_MAP_ALLOCATABLE (or with no flags) and gain access to the same physical memory that way.

    POSIX_TYPED_MEM_ALLOCATE_CONTIG is like MAP_ANON | MAP_SHARED, in that it causes a contiguous allocation.

  • The POSIX_TYPED_MEM_MAP_ALLOCATABLE case, which is used to create a mapping to an object without allocation or deallocation. This is equivalent to a shared mapping to physical memory.

You should use only MAP_SHARED mappings, since a write to a MAP_PRIVATE mapping creates a private copy for the process in normal anonymous memory.

If you specify no flag, or you specify POSIX_TYPED_MEM_MAP_ALLOCATABLE, the offset parameter to mmap() specifies the starting physical address in the typed memory region; if the typed memory region is discontiguous (multiple asinfo entries), the allowed offset values are also discontiguous and don't start at zero as they do for shared memory objects. If you specify a [paddr, paddr + size) region that falls outside the allowed addresses for the typed memory object, mmap() fails with ENXIO.

Permissions and typed memory objects

Permissions on a typed memory object are governed by UNIX permissions. The oflags argument to posix_typed_mem_open() specifies the desired access privilege, and these flags are checked against the permission mask of the typed memory object.

POSIX doesn't specify how permissions are assigned to the typed memory objects. Under BlackBerry 10 OS, default permissions are assigned at system boot-up. By default, root is the owner and group, and has read-write permissions; no one else has any permissions.

Currently, there's no mechanism to change the permissions of an object. In the future, the implementation may be extended to allow chmod() and chown() to modify the permissions.

Object length and offset definitions

You can retrieve the size of an object by using posix_typed_mem_get_info(). The posix_typed_mem_get_info() call fills in a posix_typed_mem_info structure, which includes the posix_tmi_length field, which contains the size of the typed memory object.

As specified by POSIX, the length field is dynamic and contains the current allocatable size for that object (in effect, the free size of the object for POSIX_TYPED_MEM_ALLOCATE and POSIX_TYPED_MEM_ALLOCATE_CONTIG). If you opened the object with a tflag of 0 or POSIX_TYPED_MEM_MAP_ALLOCATABLE, the length field is set to zero.

When you map in a typed memory object, you usually pass an offset to mmap(). The offset is the physical address of the location in the object where the mapping should commence. The offset is appropriate only when opening the object with a tflag of 0 or POSIX_TYPED_MEM_MAP_ALLOCATABLE. If you opened the typed memory object with POSIX_TYPED_MEM_ALLOCATE or POSIX_TYPED_MEM_ALLOCATE_CONTIG, a nonzero offset causes the call to mmap() to fail with an error of EINVAL.

Interaction with other POSIX APIs

Typed memory can interact with other POSIX APIs.

rlimits
The POSIX setrlimit() APIs provide the ability to set limits on the virtual and physical memory that a process can consume. Since typed memory operations may operate on normal RAM (sysram) and creates mappings in the process's address space, they need to be taken into account when doing the rlimit accounting. In particular, the following rules apply:
  • Any mapping created by mmap() for typed memory objects is counted in the process's RLIMIT_VMEM or RLIMIT_AS limit.
  • Typed memory never counts against RLIMIT_DATA.
POSIX file-descriptor functions
You can use the file descriptor that posix_typed_memory_open() returns with selected POSIX fd-based calls, as follows:
  • fstat(fd,..), which fills in the stat structure as it does for a shared memory object, except that the size field doesn't hold the size of the typed memory object.
  • close(fd) closes the file descriptor.
  • dup() and dup2() duplicate the file handle.
  • posix_mem_offset() behaves as documented in the POSIX specification.

Practical examples

Here are some examples of how you could use typed memory.

Allocating contiguous memory from system RAM

Here's a code snippet that allocates contiguous memory from system RAM:

int fd = posix_typed_mem_open( "/memory/ram/sysram", O_RDWR,
            POSIX_TYPED_MEM_ALLOCATE_CONTIG);

void *vaddr = mmap( NULL, size, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE, fd, 0);

Defining packet memory and allocating from it

Assume you have special memory (say fast SRAM) that you want to use for packet memory. This SRAM isn't put in the global system RAM pool. Instead, in startup, we use as_add to add an asinfo entry for the packet memory:

as_add(phys_addr, phys_addr + size - 1, AS_ATTR_NONE,
       "packet_memory", mem_id);

where phys_addr is the physical address of the SRAM, size is the SRAM size, and mem_id is the ID of the parent (typically memory, which is returned by as_default()).

This code creates an asinfo entry for packet_memory, which you can then use as POSIX typed memory. The following code allows different applications to allocate pages from packet_memory:

int fd = posix_typed_mem_open( "packet_memory", O_RDWR,
            POSIX_TYPED_MEM_ALLOCATE);
void *vaddr = mmap( NULL, size, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);

Alternatively, you may want to use the packet memory as direct shared, physical buffers. In this case, applications would use it as follows:

int fd = posix_typed_mem_open( "packet_memory", O_RDWR,
            POSIX_TYPED_MEM_MAP_ALLOCATABLE);
void *vaddr = mmap( NULL, size, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, offset);

Defining a DMA-safe region

On some hardware, due to limitations of the chipset or memory controller, it may not be possible to perform DMA to arbitrary addresses in the system. In some cases, the chipset has only the ability to DMA to a subset of all physical RAM. This has traditionally been difficult to solve without statically reserving some portion of RAM of driver DMA buffers (which is potentially wasteful). Typed memory provides a clean abstraction to solve this issue. Here's an example:

In startup, use as_add_containing() to define an asinfo entry for DMA-safe memory. Make this entry be a child of ram:

as_add_containing( dma_addr, dma_addr + size - 1,
                   AS_ATTR_RAM, "dma", "ram");
 

where dma_addr is the start of the DMA-safe RAM, and size is the size of the DMA-safe region.

This code creates an asinfo entry for dma, which is a child of ram. Drivers can then use it to allocate DMA-safe buffers:

int fd = posix_typed_mem_open( "ram/dma", O_RDWR,
            POSIX_TYPED_MEM_ALLOCATE_CONTIG);
void *vaddr = mmap( NULL, size, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus