by Nicolas Thomas

Locking hardware resources for exclusive access

Ubrflgr
3 min readSep 9, 2018

--

On embedded platforms often several processes have to use the same hardware resource, for example an eeprom. If two processes want to write to that eeprom at the same time, it may happen that one process overwrites the changes of the other — a typical race condition.

Advisory Lock

A possible solution for this problem is to use lock files that are associated to the hardware resources that have to be protected. By setting an exclusive advisory lock on a lock file, a process indicates that it is currently writing to the device that is associated to this file. The lock file is just an ordinary file placed somewhere where each process has access, for example in the tmp directory /tmp/dev/mydevice/device.lock

The operating system has to guarantee that an exclusive advisory lock on a file can only be placed by one process at any given time.

Setting the lock

Initially, the lock file doesn’t exist or may have been deleted during a system reboot. Therefore, the process that requires access to the shared resource first has to create the lock file. It has to be specified where this file will be located, otherwise it’s possible that each process checks a different lock file, which would defeat the purpose.

For example, if process A has created the lock file it would open the lock file and place an exclusive advisory lock before writing to the hardware resource. If process B wants to place a lock on the same file, it will fail and has to wait and try again later.

If the process holding the lock is killed while holding the lock, the lock is automatically removed, this must be guaranteed by the operating system.

Linux API flock

Setting advisory locks is provided by the linux API with the flock(2) function.

int flock(int fd, int operations)operations:
LOCK_EX - Place an exclusive lock.
LOCK_UN - Remove an existing lock held by this process

flock() is a blocking call and therefore the calling process is suspended until the file gets unlocked or it gets woken up for another reason. If the call should be non-blocking the LOCK_NB must be set.

Of course, this technique does not prevent a process from writing to the device per se. It only works as long as everyone sticks to the rules.

Code example

The following code example start two processes A and B, both write 10 times to a device my_device at arbitrary intervals between 1 and 10ms. In each write cycle they write the process name and cycle count to the device.

Note: The first call to flock()is a non blocking call. This is done just to be able to print if a call would block but it can be omitted.

For sake of clarity the code is reduced to the essential and does not compile as it is below. You can find a working example here: https://github.com/ubrflgr/flock

static char *lock_file = "/tmp/device.lock";static void process_run(char *p_name, int wr_cycles)
{
int lock_fd;
struct device my_device;
unsigned int interval_ms;
interval_ms = get_interval_ms(1, 10); printf("Starting process %s
Interval = %u\n", p_name, interval_ms);
lock_fd = open(lock_file, O_CREAT | O_RDONLY, 0666);

create_device(&my_device);
while (wr_cycles--)
{
if (flock(lock_fd, LOCK_EX | LOCK_NB))
{
printf("%s blocked\n", p_name);
flock(lock_fd, LOCK_EX);
}
write_to_device(&my_device, p_name, wr_cycles); flock(lock_fd, LOCK_UN); usleep(interval_ms * 1000);
}
destroy_device(&my_device);
close(lock_fd);
}
int main(int argc, char **argv)
{
if (fork() == 0)
{
process_run("A", 10);
}
else
{
process_run("B", 10);
}
return 0;
}

Following output is generated:
Process B was started first and therefore got the lock first, A was blocked and both get blocked later again.

Starting process B
interval = 3
Starting process A
interval = 4
A blocked
B: 9
A: 9
B: 8
A: 8
B: 7
A: 7
B: 6
B blocked
A: 6
B: 5
A blocked
B: 4
A: 5
B: 3
A: 4
B: 2
A: 3
B: 1
B blocked
A: 2
B: 0
A: 1

--

--