Applying C - Memory Mapped Files
Written by Harry Fairhead   
Tuesday, 26 December 2023
Article Index
Applying C - Memory Mapped Files
Memory Mapping Files
Shared Memory
Semaphore Locking

Semaphore Locking

To add locking you need to make use of a semaphore which works between processes or threads. A semaphore is a more than just a lock, it is a way of signaling a state. A semaphore is created and set to a value. When a process waits on a semaphore it checks the value of the semaphore and as long as the value is greater than zero it decrements the value and continues - i.e. it has a lock. If the value is zero the process blocks and waits for the value to be greater than zero - i.e. it has to wait for the lock. Unlocking the semaphore immediately increments the value by one.

Semaphores come in named and unnamed versions. You need a named semaphore to lock between processes, but an unnamed semaphore will suffice for locking threads. To create a semaphore you first need to create a variable of type sem_t, then you use the sem_open function for a named semaphore or sem_init for an unnamed semaphore. A named semaphore is removed using sem_close and an unnamed semaphore is removed using sem_destroy.

After creation named and unnamed semaphores are used in the same way with sem_wait being an attempt to decrement and lock and sem_post being an unlock and increment. Notice that although we have been using the terms “lock” and “unlock”, exactly what a semaphore is used for is up to you.

To create a named semaphore you use:

sem_t* semptr = sem_open(Name,flags,mode,value);

where Name is a null terminated string starting with / and not containing any additional slashes. It is the name of the semaphore used by all of the processes wanting to access it. The flags and mode parameters are as for opening a file. If you are creating the semaphore with O_CREATE then you have to supply value which is used to set the semaphore's initial value. If the semaphore already exists mode and value are ignored.

To wait on a semaphore or obtain a lock you use:

sem_wait(semptr);

to release a "lock" you would use:

sem_post(semptr);

When a process has finished with a semaphore it can close it using:

sem_close(semptr);

and if necessary it can be removed using:

sem_unlink(name);

where name is the name used to open it.

Notice that if you create a named semaphore with initial value 1 then only one process can acquire a lock and any process that tries to acquire a lock has to wait until the first process releases it.

Generally the initial value gives the number of processes that can lock the semaphore without having to wait.

Using a semaphore in this way the writer program is:

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>       
#include <fcntl.h>  
#include <unistd.h>
#include <sys/types.h>
#include <inttypes.h>
#include <semaphore.h>
int main(int argc, char** argv) {
    int value = 0x55555555;
    
    int fd = shm_open("BackingFile", O_RDWR |
O_CREAT, 0644); ftruncate(fd, 1000); uint8_t *memptr = mmap(NULL, 1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); sem_unlink("/mySemaphore"); sem_t* semptr = sem_open("/mySemaphore", O_CREAT,
0644, 1); for (int i = 0; i < 20; ++i) { value = ~value; sem_wait(semptr); memcpy(memptr, &value, 4); sem_post(semptr); printf("%x\n", value); fflush(NULL); sleep(1); } munmap(memptr, 1000); close(fd); shm_unlink("BackingFile"); return (EXIT_SUCCESS);

}

Notice that we unlink the semaphore just before we attempt to create it to make sure that we set its initial value. The writing program then tries to acquire a lock before writing to the memory location. If it acquires the lock, we can be sure that the reader isn't in the middle of reading data. It relinquishes the lock as soon as it can.

The reader program is very similar:

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>       
#include <fcntl.h>  
#include <unistd.h>
#include <sys/types.h>
#include <inttypes.h>
#include <semaphore.h>
int main(int argc, char** argv) {
    int value = 0x55555555;
    
    int fd = shm_open("BackingFile", O_RDWR, 0644);
    
    uint8_t *memptr = mmap(NULL, 1000, PROT_READ |
                        PROT_WRITE, MAP_SHARED, fd, 0);
    sem_t* semptr = sem_open("/mySemaphore", O_CREAT,
0644, 1); for (int i = 0; i < 10; ++i) { sem_wait(semptr); memcpy(&value, memptr, 4); sem_post(semptr); printf("%x\n", value); fflush(NULL); sleep(1); } munmap(memptr, 1000); close(fd); shm_unlink("BackingFile"); return (EXIT_SUCCESS); }

In this case we do not unlink the semaphore before opening it because we don't want to change its value if the writer has already created it - it is assumed that the write is started first as if it isn't the attempt to read the shared memory fails. That is, the semaphore mechanism doesn't stop the reader from attempting to read before the writer has started. Again the reader locks the semaphore before reading and unlocks it as quickly as possible. For either program to work we have to add the pthread library via the linker.

Semaphores can be used to implement more complex types of synchronization and a full coverage of the topic would take a book in its own right. This chapter has given you a brief introduction to the many parts of the pseudo file system. In general there are two problems with making use of it. The first is discovering if there is a part of the system that covers the device or facility you want to access. The second is discovering if the machine you are working with implements it and how well.

Summary

  • The general principle of “everything is a file” leads to the pseudo file system which wraps many hardware sources and sinks of data as files and folders.

  • From an IoT point of view the most important of the pseudo directories is sysfs which provides access to the GPIO.

  • Working with GPIO lines using sysfs is a matter of exporting the line you want to use and then using the folders and files created to manipulate it.

  • Sysfs has a reputation for being slow, but this is mainly due to the closing and reopening of files on each access. If you make use of fflush or file descriptors then it is much faster, although still slower than direct access to the GPIO.

  • To access memory directly with the cooperation of the memory management hardware you have to use the mem pseudo file. Reading and writing this file gives you direct access to memory.

  • Any file can be loaded into a process’s memory and accessed directly using pointers. This is faster than repeated disk accesses.

  • If the mem pseudo file is loaded into a process's memory then you can have direct access to a machine’s memory using pointers rather than file operations. This is the standard way of accessing memory-mapped I/O on most machines.

  • A memory mapped file can be shared between processes.

  • To lock a memory mapped file use a semaphore.

Now available as a paperback or ebook from Amazon.

Applying C For The IoT With Linux

  1. C,IoT, POSIX & LINUX
  2. Kernel Mode, User Mode & Syscall
  3. Execution, Permissions & Systemd
    Extract Running Programs With Systemd
  4. Signals & Exceptions
    Extract  Signals
  5. Integer Arithmetic
    Extract: Basic Arithmetic As Bit Operations
  6. Fixed Point
    Extract: Simple Fixed Point Arithmetic
  7. Floating Point
  8. File Descriptors
    Extract: Simple File Descriptors 
    Extract: Pipes 
  9. The Pseudo-File System
    Extract: The Pseudo File System
    Extract: Memory Mapped Files ***NEW
  10. Graphics
    Extract: framebuffer
  11. Sockets
    Extract: Sockets The Client
    Extract: Socket Server
  12. Threading
    Extract:  Pthreads
    Extract:  Condition Variables
    Extract:  Deadline Scheduling
  13. Cores Atomics & Memory Management
    Extract: Applying C - Cores 
  14. Interupts & Polling
    Extract: Interrupts & Polling 
  15. Assembler
    Extract: Assembler

Also see the companion book: Fundamental C

<ASIN:1871962609>

<ASIN:1871962463>

<ASIN:1871962617>

<ASIN:1871962455>

Related Articles

Remote C/C++ Development With NetBeans

Raspberry Pi And The IoT In C

Getting Started With C/C++ On The Micro:bit

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


Pure Virtual C++ 2024 Sessions Announced
19/04/2024

Microsoft has announced the sessions for Pure Virtual C++ 2024, which is taking place on April 30th 15:00 UTC. People who sign up will get access to five sessions happening on the day, alongside a ran [ ... ]



Node.js 22 Adds WebSocket Client
29/04/2024

Node.js 22 has been released with support for requiring ESM graphs, a stable WebSocket client, and updates of the V8 JavaScript engine.


More News

raspberry pi books

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Wednesday, 27 December 2023 )