Linux shared memory permissions

POSIX Shared Memory in Linux

1.0 Shared Memory

Shared memory is the fastest method of interprocess communication (IPC) under Linux and other Unix-like systems. The system provides a shared memory segment which the calling process can map to its address space. After that, it behaves just like any other part of the process’s address space.

2.0 Why is shared memory the fastest IPC mechanism?

If we look at the other IPC mechanisms like message queue or the older mechanisms like the pipe or the fifo, the work required for passing message involves, first copying the message from the address space of the first process to the kernel space via a send-like system call and, then, copying the message from the kernel space to the address space of the second process during a receive-like call. In the case of shared memory, the shared memory segment is mapped to the address space of both processes. As soon as the first process writes data in the shared memory segment, it becomes available to the second process. This makes shared memory faster than other mechanisms and is, in fact, the fastest way of passing data between two processes on the same host system.

3.0 System V and POSIX Shared Memory Calls

For each of the IPC mechanisms, there are two sets of calls, the traditional System V calls and the newer POSIX calls. In this post, we will look at the POSIX shared memory calls. Example programs for server and client processes that communicate via POSIX shared memory are given near the end of this post.

4.0 POSIX Shared Memory Calls

The POSIX shared memory calls seem to be based on the UNIX philosophy that if you do Input/Output operations on an object, that object has to be a file. So, since we do read and write to a POSIX shared memory object, the latter is to be treated as a file. A POSIX shared memory object is a memory-mapped file. POSIX shared memory files are provided from a tmpfs filesystem mounted at /dev/shm . The individual shared memory files are created using the shm_open system call under /dev/shm . There are just two specialized POSIX shared memory system calls, shm_open and shm_unlink , which are analogous to open and unlink system calls for files. Other operations on POSIX shared memory are done using the ftruncate , mmap and munmap system calls for files. A program using POSIX shared memory calls needs to be linked with -lrt .

4.1 shm_open

shm_open is like the open system call for files. It opens a POSIX shared memory object and makes it available to the calling process via the returned file descriptor. The first parameter, name, is the name of the shared memory object and is of the form /somename, that is, it is a null-terminated string of a maximum of NAME_MAX characters, and, its first character is a slash and none of the other characters can be a slash. oflag is a bit mask constructed by OR-ing either O_RDONLY or O_RDWR with one or more of the following flags. O_CREAT creates the shared memory object if it does not exist. And, if it creates the object, the last nine bits of the third parameter, mode are taken for permissions except that the bits set in the file mode creation mask are cleared for the object. Also, if the shared memory object is created, the owner and group ids of the object are set the corresponding effective ids of the calling process. A newly created shared memory object is of size zero bytes. It can be made of the desired size by using the ftruncate system call. If O_EXCL flag is used together with the O_CREAT flag, and the shared memory object for the name already exists, shm_open fails and errno is set to EEXIST . There is another flag, O_TRUNC , which if specified, truncates the shared memory object, if it already exists, to size zero bytes. On success, shm_open returns the file descriptor for the shared memory object. If shm_open fails, it returns -1 and errno is set to the cause of the error.

shm_unlink removes the previously created POSIX shared memory object. The name is the name of the shared memory object as described under shm_open , above.

5.0 Other System Calls used in POSIX Shared Memory Operations

5.1 ftruncate

The ftruncate system call makes the object referred to by the file descriptor, fd, of size length bytes. When a POSIX shared memory is created, it is of size zero bytes. Using ftruncate , we can make the POSIX shared memory object of size length bytes. ftruncate returns zero on success. In case of error, ftruncate returns -1 and errno is set to the cause of the error.

5.2 mmap

With the mmap system call, we can map a POSIX shared memory object to the calling process’s virtual address space. addr specifies the address at which it should be mapped. In most cases, we do not care at what address mapping is done and a value of NULL for addr should suffice. length is the length of shared memory object that should be mapped. To keep things simple, we will map the whole object and length for us will be the length of the shared memory object. prot can have the values, PROT_EXEC , PROT_READ , PROT_WRITE and PROT_NONE . PROT_EXEC means that the mapped pages may be executed and PROT_NONE means that the mapped pages may not be accessed. These two values do not make sense for a shared memory object. So we will use PROT_READ | PROT_WRITE value for prot. There are many flags but the only one meaningful for shared memory is MAP_SHARED , which means that the updates to the mapped shared memory are visible to all other processes immediately. fd is, of course, the file descriptor for the shared memory received from an earlier shm_open call. offset is the location in the shared memory object at which the mapping starts; we will use the value zero for offset and map the shared memory object starting right from the beginning. On success, mmap returns the pointer to the location where the shared memory object has been mapped. In case of error, MAP_FAILED , which is, (void *) -1 , is returned and errno is set to the cause of the error.

Читайте также:  Asus k52j драйвера для веб камеры windows 10

5.3 munmap

munmap unmapps the shared memory object at location pointed by addr and having size, length. On success, munmap returns 0. In case of error, munmap returns -1 and errno is set to the cause of the error.

6.0 An example: System Logger

The System Logger process creates a POSIX shared memory object and maps it to its address space. Clients also map the shared memory object to their address spaces. When a client wants to log a message, it creates a string in the format and writes the string in the shared memory object. The logger reads strings from the shared memory object, one by one, and writes them in a log file in the chronological order.

The server code is,

And, the client code is,

We can compile and run the server and a client as below.

The Logger writes all messages to the /tmp/example.log file. We can see the contents of the log file with the tail -f command.

Since the Logger runs in an infinite loop and is terminated with a Control-C or the kill command, the semaphore files are left lying in the /dev/shm directory. These need to be removed between successive runs of Logger. That is,

Источник

Linux System Calls

This chapter is from the book

This chapter is from the book

This chapter is from the book 

So far, we’ve presented a variety of functions that your program can invoke to perform system-related functions, such as parsing command-line options, manipulating processes, and mapping memory. If you look under the hood, you’ll find that these functions fall into two categories, based on how they are implemented.

A library function is an ordinary function that resides in a library external to your program. Most of the library functions we’ve presented so far are in the standard C library, libc. For example, getopt_long and mkstemp are functions provided in the C library.

A call to a library function is just like any other function call. The arguments are placed in processor registers or onto the stack, and execution is transferred to the start of the function’s code, which typically resides in a loaded shared library.

A system call is implemented in the Linux kernel. When a program makes a system call, the arguments are packaged up and handed to the kernel, which takes over execution of the program until the call completes. A system call isn’t an ordinary function call, and a special procedure is required to transfer control to the kernel. However, the GNU C library (the implementation of the standard C library provided with GNU/Linux systems) wraps Linux system calls with functions so that you can call them easily. Low-level I/O functions such as open and read are examples of system calls on Linux.

The set of Linux system calls forms the most basic interface between programs and the Linux kernel. Each call presents a basic operation or capability.

Some system calls are very powerful and can exert great influence on the system. For instance, some system calls enable you to shut down the Linux system or to allocate system resources and prevent other users from accessing them. These calls have the restriction that only processes running with superuser privilege (programs run by the root account) can invoke them. These calls fail if invoked by a nonsuperuser process.

Note that a library function may invoke one or more other library functions or system calls as part of its implementation.

Linux currently provides about 200 different system calls. A listing of system calls for your version of the Linux kernel is in /usr/include/asm/unistd.h. Some of these are for internal use by the system, and others are used only in implementing specialized library functions. In this chapter, we’ll present a selection of system calls that are likely to be the most useful to application and system programmers.

Most of these system calls are declared in .

8.1 Using strace

Before we start discussing system calls, it will be useful to present a command with which you can learn about and debug system calls. The strace command traces the execution of another program, listing any system calls the program makes and any signals it receives.

To watch the system calls and signals in a program, simply invoke strace, followed by the program and its command-line arguments. For example, to watch the system calls that are invoked by the hostname 1 command, use this command:

Читайте также:  Windows для устаревших компьютеров

This produces a couple screens of output. Each line corresponds to a single system call. For each call, the system call’s name is listed, followed by its arguments (or abbreviated arguments, if they are very long) and its return value. Where possible, strace conveniently displays symbolic names instead of numerical values for arguments and return values, and it displays the fields of structures passed by a pointer into the system call. Note that strace does not show ordinary function calls.

In the output from strace hostname, the first line shows the execve system call that invokes the hostname program: 2

The first argument is the name of the program to run; the second is its argument list, consisting of only a single element; and the third is its environment list, which strace omits for brevity. The next 30 or so lines are part of the mechanism that loads the standard C library from a shared library file.

Toward the end are system calls that actually help do the program’s work. The uname system call is used to obtain the system’s hostname from the kernel,

Observe that strace helpfully labels the fields (sys and node) of the structure argument. This structure is filled in by the system call—Linux sets the sys field to the operating system name and the node field to the system’s hostname. The uname call is discussed further in Section 8.15, «uname

Finally, the write system call produces output. Recall that file descriptor 1 corresponds to standard output. The third argument is the number of characters to write, and the return value is the number of characters that were actually written.

This may appear garbled when you run strace because the output from the hostname program itself is mixed in with the output from strace.

If the program you’re tracing produces lots of output, it is sometimes more convenient to redirect the output from strace into a file. Use the option -o filename to do this.

Understanding all the output from strace requires detailed familiarity with the design of the Linux kernel and execution environment. Much of this is of limited interest to application programmers. However, some understanding is useful for debugging tricky problems or understanding how other programs work.

Источник

Inter-process communication in Linux: Shared files and shared memory

Learn how processes synchronize with each other in Linux.

Subscribe now

Get the highlights in your inbox every week.

This is the first article in a series about interprocess communication (IPC) in Linux. The series uses code examples in C to clarify the following IPC mechanisms:

  • Shared files
  • Shared memory (with semaphores)
  • Pipes (named and unnamed)
  • Message queues
  • Sockets
  • Signals

This article reviews some core concepts before moving on to the first two of these mechanisms: shared files and shared memory.

Core concepts

A process is a program in execution, and each process has its own address space, which comprises the memory locations that the process is allowed to access. A process has one or more threads of execution, which are sequences of executable instructions: a single-threaded process has just one thread, whereas a multi-threaded process has more than one thread. Threads within a process share various resources, in particular, address space. Accordingly, threads within a process can communicate straightforwardly through shared memory, although some modern languages (e.g., Go) encourage a more disciplined approach such as the use of thread-safe channels. Of interest here is that different processes, by default, do not share memory.

There are various ways to launch processes that then communicate, and two ways dominate in the examples that follow:

  • A terminal is used to start one process, and perhaps a different terminal is used to start another.
  • The system function fork is called within one process (the parent) to spawn another process (the child).

The first examples take the terminal approach. The code examples are available in a ZIP file on my website.

Shared files

Programmers are all too familiar with file access, including the many pitfalls (non-existent files, bad file permissions, and so on) that beset the use of files in programs. Nonetheless, shared files may be the most basic IPC mechanism. Consider the relatively simple case in which one process (producer) creates and writes to a file, and another process (consumer) reads from this same file:

The obvious challenge in using this IPC mechanism is that a race condition might arise: the producer and the consumer might access the file at exactly the same time, thereby making the outcome indeterminate. To avoid a race condition, the file must be locked in a way that prevents a conflict between a write operation and any another operation, whether a read or a write. The locking API in the standard system library can be summarized as follows:

  • A producer should gain an exclusive lock on the file before writing to the file. An exclusive lock can be held by one process at most, which rules out a race condition because no other process can access the file until the lock is released.
  • A consumer should gain at least a shared lock on the file before reading from the file. Multiple readers can hold a shared lock at the same time, but no writer can access a file when even a single reader holds a shared lock.

A shared lock promotes efficiency. If one process is just reading a file and not changing its contents, there is no reason to prevent other processes from doing the same. Writing, however, clearly demands exclusive access to a file.

The standard I/O library includes a utility function named fcntl that can be used to inspect and manipulate both exclusive and shared locks on a file. The function works through a file descriptor, a non-negative integer value that, within a process, identifies a file. (Different file descriptors in different processes may identify the same physical file.) For file locking, Linux provides the library function flock, which is a thin wrapper around fcntl. The first example uses the fcntl function to expose API details.

Читайте также:  Как успокоить windows 10

Example 1. The producer program

The main steps in the producer program above can be summarized as follows:

  • The program declares a variable of type struct flock, which represents a lock, and initializes the structure’s five fields. The first initialization: makes the lock an exclusive (read-write) rather than a shared (read-only) lock. If the producer gains the lock, then no other process will be able to write or read the file until the producer releases the lock, either explicitly with the appropriate call to fcntl or implicitly by closing the file. (When the process terminates, any opened files would be closed automatically, thereby releasing the lock.)
  • The program then initializes the remaining fields. The chief effect is that the entire file is to be locked. However, the locking API allows only designated bytes to be locked. For example, if the file contains multiple text records, then a single record (or even part of a record) could be locked and the rest left unlocked.
  • The first call to fcntl: tries to lock the file exclusively, checking whether the call succeeded. In general, the fcntl function returns -1 (hence, less than zero) to indicate failure. The second argument F_SETLK means that the call to fcntl does not block: the function returns immediately, either granting the lock or indicating failure. If the flag F_SETLKW (the W at the end is for wait) were used instead, the call to fcntl would block until gaining the lock was possible. In the calls to fcntl, the first argument fd is the file descriptor, the second argument specifies the action to be taken (in this case, F_SETLK for setting the lock), and the third argument is the address of the lock structure (in this case, &lock).
  • If the producer gains the lock, the program writes two text records to the file.
  • After writing to the file, the producer changes the lock structure’s l_type field to the unlock value: and calls fcntl to perform the unlocking operation. The program finishes up by closing the file and exiting.

Example 2. The consumer program

The consumer program is more complicated than necessary to highlight features of the locking API. In particular, the consumer program first checks whether the file is exclusively locked and only then tries to gain a shared lock. The relevant code is:

The F_GETLK operation specified in the fcntl call checks for a lock, in this case, an exclusive lock given as F_WRLCK in the first statement above. If the specified lock does not exist, then the fcntl call automatically changes the lock type field to F_UNLCK to indicate this fact. If the file is exclusively locked, the consumer terminates. (A more robust version of the program might have the consumer sleep a bit and try again several times.)

If the file is not currently locked, then the consumer tries to gain a shared (read-only) lock (F_RDLCK). To shorten the program, the F_GETLK call to fcntl could be dropped because the F_RDLCK call would fail if a read-write lock already were held by some other process. Recall that a read-only lock does prevent any other process from writing to the file, but allows other processes to read from the file. In short, a shared lock can be held by multiple processes. After gaining a shared lock, the consumer program reads the bytes one at a time from the file, prints the bytes to the standard output, releases the lock, closes the file, and terminates.

Here is the output from the two programs launched from the same terminal with % as the command line prompt:

In this first code example, the data shared through IPC is text: two lines from Shakespeare’s play Richard III. Yet, the shared file’s contents could be voluminous, arbitrary bytes (e.g., a digitized movie), which makes file sharing an impressively flexible IPC mechanism. The downside is that file access is relatively slow, whether the access involves reading or writing. As always, programming comes with tradeoffs. The next example has the upside of IPC through shared memory, rather than shared files, with a corresponding boost in performance.

Shared memory

Linux systems provide two separate APIs for shared memory: the legacy System V API and the more recent POSIX one. These APIs should never be mixed in a single application, however. A downside of the POSIX approach is that features are still in development and dependent upon the installed kernel version, which impacts code portability. For example, the POSIX API, by default, implements shared memory as a memory-mapped file: for a shared memory segment, the system maintains a backing file with corresponding contents. Shared memory under POSIX can be configured without a backing file, but this may impact portability. My example uses the POSIX API with a backing file, which combines the benefits of memory access (speed) and file storage (persistence).

The shared-memory example has two programs, named memwriter and memreader, and uses a semaphore to coordinate their access to the shared memory. Whenever shared memory comes into the picture with a writer, whether in multi-processing or multi-threading, so does the risk of a memory-based race condition; hence, the semaphore is used to coordinate (synchronize) access to the shared memory.

The memwriter program should be started first in its own terminal. The memreader program then can be started (within a dozen seconds) in its own terminal. The output from the memreader is:

Источник

Оцените статью