Cover image for Linux System Application Programming

Linux System Application Programming


Timeline

Timeline

2025-11-01

init

This article introduces the basics of Linux system application programming, elaborates on the installation of the man manual and the topic classification of its chapters, and systematically discusses file I/O operations under Linux, with a focus on analyzing the usage, parameter flags, and return values of system call functions such as open(), close(), and read().

man

Installation

1
sudo pacman -Sy man-pages

This method installs the man-pages that match the current system’s glibc version.

You can also directly query the man-pages for various Ubuntu versions on the web.

Content

SectionTopic ClassificationContent ExamplesTypical Uses
1User Commandsls(1),grep(1),man(1)Common shell commands, executable programs
2System Callsopen(2),read(2),fork(2)System call interface provided by the kernel (C language level)
3C Library Functionsprintf(3),malloc(3),strcpy(3)Standard C Library (libc) and Other Library Functions
4Devices and Special Filesnull(4),tty(4)/devDevice File Interface Under
5File Formats and Conventionspasswd(5),fstab(5)Configuration File Format Description
6Gamesfortune(6)Historical Legacy, Now Rarely Used
7Concepts and Protocols (Miscellaneous / Conventions / Protocols)signal(7),socket(7),regex(7)Semantic or Systematic Documentation (Protocols, Macros, Standards, etc.)
8System Administration Commandsmount(8),ifconfig(8),systemd(8)Commands Used Only by Root or Administrators
9Kernel Developer Interface (Kernel Developer Docs, Non-standard)request_irq(9)(Included in Kernel Source Code)Kernel Programming API (Only Appears in Kernel Source Documentation)

Usage

1
man 2 open

I/O Operations in Linux

File I/O

open()

Functionint open(const char *pathname, int flags)
int open(const char *pathname, int flags, mode_t mode)
Header File#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
ParameterspathnamePath and Filename
ParametersflagsFile open mode, can be set by bitwise OR of multiple flags
ParametersmodePermission mask, sets executable, read, write permissions for different users and groups, expressed in octal; this parameter is optional
Return ValueIf open() executes successfully, itreturns an int file descriptor, and returns -1 on error.
FunctionThrough a system call, you can open a file and return a file descriptor.

Optional flags for the parameter flags:

  • O_CREAT
    Automatically create the file if the file name to be opened does not exist.

  • O_EXCL
    Must be used together with O_CREAT to take effect; if the file exists, the open() call fails.

  • O_RDONLY
    Open the file in read-only mode.

  • O_WRONLY
    Open the file in write-only mode.

  • O_RDWR
    Open the file in read-write mode.

  • O_APPEND
    Open the file in append mode.

  • O_NONBLOCK

Open in non-blocking mode.

close()

Functionint close(int fd)
Header file#include <unistd.h>
ParametersfdFile descriptor
Return valueReturns 0 on success; returns -1 on error.

read()

Functionssize_t read(int fd, void *buf, size_t count)
Header file#include <unistd.h>
ParameterfdFile descriptor to read from
ParameterbufBuffer to store the read content
ParametercountNumber of bytes to read each time
Return valueReturn value greater than 0 indicates the number of bytes read;
Equal to 0 in blocking mode indicates end of file or no data to read (EOF), and blocks;
Equal to -1 indicates an error, or in non-blocking mode indicates no data to read.

write()

Functionssize_t write(int fd, const void *buf, size_t count);
Header file#include <unistd.h>
ParametersfdFile descriptor
ParametersbufBuffer, storing data to be written
ParameterscountNumber of bytes to write each time (originally ‘count’ is more accurately bytes)
FunctionRead count bytes from the buf buffer and write to the file identified by fd
Return valueGreater than or equal to 0 indicates success, returns the number of bytes written;
Returns -1 indicates an error

Note that write permission is required when opening

lseek()

All open files have aCurrent file offset, hereinafter referred to as cfo. cfo is usually anon-negative integer, used to indicate the number of bytes from the beginning of the file to the current file position. Read and write operations typically start at cfo and increase cfo by the number of bytes read or written. When a file is opened, cfo is initialized to 0 unless O_APPEND is used. The lseek function can be used to change the file’s cfo.

ItemDescription
Function Definitionoff_t lseek(int fd, off_t offset, int whence);
Header File#include <sys/types.h>
#include <unistd.h>
ParameterfdFile Descriptor
Parameteroff_t offsetOffset,in bytespositive and negative indicate moving forward and backward respectively
ParameterwhencePosition Base, optionalSEEK_SET(beginning of file),SEEK_CUR(current pointer position),SEEK_END(end of file)
FunctionMove the file read/write pointer; get the file length; expand the file space
Return ValueSuccessReturns the current offset (SEEK_CUR), returns -1 on failure

Example

  • Set the file position pointer to 100 (beginning + 100 bytes)

    1
    lseek(fd,100,SEEK_SET);
  • Set the file position to the end of the file

    1
    lseek(fd,0,SEEK_END);
  • Determine the current file position

    1
    lseek(fd,0,SEEK_CUR);

access()

1
2
3
#include <unistd.h>

int access(const char *path, int amode);
ItemContent
Header file#include <unistd.h>
Function Prototypeint access(const char *path, int amode);
PurposeCheck the specified pathpathFor the calling process’s**real user ID (real UID) and group ID (real GID)**whether it is accessible (e.g., readable, writable, executable, or exists).
Parameterspath: The file path to check.
amode: The access mode to check, one of the following constants or their bitwise OR:
F_OK: Whether the file exists
R_OK: Whether it is readable
W_OK: Whether it is writable
X_OK: Whether it is executable
Return ValueReturns on success0; returns on failure-1and setserrno
common errnoEACCES: insufficient permissions
ENOENT: file does not exist
ENOTDIR: a component of the path is not a directory
EROFS: writing to a read-only file system
ELOOP: too many symbolic links
ENAMETOOLONG: path too long
behavior description- usesreal user ID and group IDto check permissions, not effective ID. - Ifamodeis a combination of multiple flags, they are checked separately. - Only checks access permissions, does not actually open the file.
notes / limitations⚠️ Not recommended due toTOCTTOU (time-of-check-to-time-of-use) race condition: the file may have been modified after permission check.
👉 Safer approach: directly attempt the operation (e.g.,open()) and catch errors.
Related functionsfaccessat()(safer, can specify directory fd)chmod()fstat()

fnctl()

Operate on file descriptors

ItemDescription
Function definitionint fcntl(int fd, int cmd, … /* arg */ );
Header file#include <unistd.h>
#include <fcntl.h>
Parameter fdThefile descriptor to operate on
Parameter cmdControl command to execute (see table below)
Parameter arg(Optional)For different cmd, the type of arg varies; it can be int, struct flock*, etc.
FunctionControls file descriptors, including: • Duplicate FD (F_DUPFD) • Set/Get FD flags (F_GETFD / F_SETFD) • Set/Get file status flags (F_GETFL / F_SETFL) • File locking (F_SETLK / F_SETLKW)
Return value• On success: returns a value depending on cmd • On failure: returns**-1**, and sets errno

Common CMD commands

cmd nameMeaningarg typeReturn value
F_DUPFDDuplicate file descriptor (≥ arg)intReturn new fd
F_GETFDGet FD flags (e.g., FD_CLOEXEC)NoneReturn flags
F_SETFDSet FD flagsint0
F_GETFLGet file status flags (O_NONBLOCK/O_APPEND, etc.)NoneReturn flags
F_SETFLSet file status flags (commonly used to set O_NONBLOCK)int0
F_SETLKSet file lock (non-blocking)struct flock*Success 0, failure -1
F_SETLKWSet file lock (blocking)struct flock*Success 0
F_GETLKTest file lock statusstruct flock*0

ioctl()

ItemDescription
Function definitionint ioctl(int fd, unsigned long request, … /* arg */ );
Header file#include <sys/ioctl.h>(May require device-specific header files, such aslinux/ioctl.h
Parameter fdOpened device file descriptor (e.g.,/dev/...
Parameter requestIO control command (usually constructed via_IO,_IOR,_IOW,_IOWRand other macros)
Parameter arg(Optional)Data associated with the request command, can beint*,void*,struct *etc.
FunctionExecute control commands on the device driver (non-data read/write type), used for configuring hardware, obtaining status, sending control instructions, etc.
Return valueSuccess: usually0(may also return other positive values, depending on the request)
Failure: returns**-1**, and sets errno

Among the above three parameters, the most important is the second cmd parameter, which is of type unsigned int. To efficiently use the cmd parameter to pass more control information, an unsigned int cmd is split into 4 segments, each with its own meaning. The bit field split of unsigned int cmd is as follows:

1
2
| 31 30 | 29 ................ 16 | 15 ...... 8 | 7 .......... 0 |
| dir | size | type | nr |
  • cmd[31:30]Transmission direction of data (args) (read/write)
  • cmd[29:16]Size of data (args)
  • cmd[15:8] **Type of command, can be understood as the key of the command,**generally an ASCII code (a character from 0-255, some characters are already occupied, and the sequence number segment of each character may be partially occupied)
  • cmd[7:0]Command sequence number, is an 8-bit number (sequence number, between 0-255)

The cmd parameter is obtained from ioctl composite macro definitions. The four composite macro definitions are as follows:

  1. Define a command without parameters:
1
2
#define _IO(type,nr)
_IOC(_IOC_NONE,(type),(nr),0)
  1. Define a command where the application reads parameters from the driver:
1
2
#define _IOR(type,nr,size)
_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command where the application writes parameters to the driver:
1
2
#define _IOW(type,nr,size)
_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command where parameters are passed bidirectionally:
1
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

The macro definition parameter descriptions are as follows:

  • type: The type of the command, generally an ASCII value. A driver typically uses one type
  • nr: The sequence number under this command. A driver has multiple commands, generally with the same type but different sequence numbers
  • size: The type of args
    For example, the following code can be used to define three macros: one without parameters, one for writing parameters to the driver, and one for reading parameters from the driver:
1
2
3
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IOW('L',1,int)
#define CMD_TEST2 _IOR('L',2,int)

Directory I/O

mkdir()

ItemDescription
Functionint mkdir(const char *pathname, mode_t mode)
Header file#include <sys/stat.h>
#include <sys/types.h>
ParameterpathnamePath and name of the directory to create
ParametermodePermission mask, sets read, write, and execute permissions for user and group in octal; this parameter can be omitted
Return valueReturns 0 on success, -1 on error
FunctionalityCreate a directory

opendir()/closedir()

FunctionDescription
Function definition**DIR opendir(const char name)
Header file#include <sys/types.h>
#include <dirent.h>
ParametersnamePathname of the directory
Return valueOn success, returns a directory stream (DIR*type)**; on failure, returnsNULL
FunctionalityOpen the specified directory and obtain a directory stream for traversing it
FunctionDescription
Function definitionint closedir(DIR *dirp)
Header file#include <sys/types.h>
#include <dirent.h>
ParametersdirpPointer to the directory stream to be closed
FunctionalityClose the directory stream and release associated resources

readdir()

1
man 3 readdir
ItemDescription
Functionstruct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
Header File#include <dirent.h>
ParametersDIR *dirpDirectory stream pointer to read
Return ValueOn success, returns a pointer to the directory entry (struct dirent*type), on failure returnsNULL
FunctionalityReads directory entries to traverse directory contents

In the glibc implementation, the dirent structure is defined as follows:

1
2
3
4
5
6
7
8
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};

Note that the returned dirent is linked in a list form, meaning each read retrieves one directory entry, requiring a loop to traverse all entries.

Library

A library is an executable binary file containing compiled code

Using libraries improves development efficiency. In Linux, there are static and dynamic libraries.

  • Static libraries are linked into the target code during program compilation. Therefore, the program no longer needs the static library at runtime. As a result, the compiled output is larger in size.Starts with ‘lib’ and ends with ‘.a’

  • Dynamic libraries (also called shared libraries) are not linked into the target code during program compilation; instead, they are loaded when the program runs. Therefore, the program needs the dynamic library at runtime. As a result, the compiled output is smaller in size.Starts with ‘lib’ and ends with ‘.so’

Static library

  • Steps to create a static library:

    1. Write or prepare the library’s source code

    2. Compile the source .c files to generate .o files

    3. Use the ar command to create the static library

    4. Test the library file

Example:

1
2
3
4
# -c: Compile only, without linking, to generate .o object files
gcc -c mylib.c -o mylib.o

ar cr libmylib.a mylib.o

Meaning of the ar command:

PartExplanation
arThe “archiver” tool, used for packaging.oFiles
ccreate: Create a new archive file (regardless of whether it exists)
rreplace: Add the target file to the archive (replace if it already exists)
libmylib.aOutput static library file name
mylib.oObject files to be added to the library

How to use a static library

StepsCommand exampleDescription
1. Write the main programmain.cUse functions provided by the library in the program
2. Compile the main program and link the static librarygcc main.c -L. -lmylib -o main-L.Specify the library file path
-lSpecify the library name (remove the lib prefix and .a suffix)
3. Run the program./mainThe static library has been linked into the executable, no additional library files are needed

Dynamic library

  • Steps to create a dynamic library:
    1. Write or prepare the library’s source code
    2. Compile the source .c files to generate .o files
    3. Use the gcc command to create the dynamic library
    4. Test the library file

Example:

1
2
gcc -c -fpic mylib.c -o mylib.o
gcc -shared -o libmylib.so mylib.o

Step 1

OptionMeaning
-cCompile only, do not link (generate.o
-fpicGeneratePosition-independent code(Position Independent Code, PIC)
mylib.cSource file
-o mylib.oOutput file name

Step 2

OptionMeaning
-sharedGenerate shared object (.sofile) instead of an executable
-o libmylib.soSpecify output file
mylib.oInput object file

The system will by default look for dynamic libraries in the /lib and /usr/lib directories. If the library we use is not there, an error will be reported.

  • First method:
    Copy the generated dynamic library to /lib or /usr/lib, because the system will by default look in these two paths.

  • Second method:
    Add the path where our dynamic library is located to the environment variable. For example, if the path of our dynamic library is /home/test, we can add it like this, but this method is only valid in the currently set window.

    1
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test/
  • The third method:
    Modify the configuration file /etc/ld.so.conf under Ubuntu, we add the location of the dynamic library in this configuration file, and thenuse the command ldconfig to update the directory

How to use dynamic libraries

StepsCommand exampleDescription
1. Write the main programmain.cUse functions provided by the library in the program
2. Compile the main program and link the dynamic librarygcc main.c -L. -lmylib -o mainSimilar to static libraries,-lspecify the library name,-Lspecify the path
3. Set the dynamic library pathexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test/If the library is not in the system default path (/lib or /usr/lib), you need to set environment variables
4. Run the program./mainThe program loads dynamic libraries when running.soFile

Process and Inter-Process Communication

Each process has a unique identifier, namely the process ID, abbreviated aspid

Methods of inter-process communication:

  1. Pipe communication: Named pipes, unnamed pipes
  2. Signal communication: Sending signals, receiving signals, handling signals
  3. IPC communication: Shared memory, message queues, semaphores
  4. Socket communication

Process basics

getpid()

Projectgetppid Function Description
Header File#include <sys/types.h>
#include <unistd.h>
Functionpid_t getppid(void);
Return ValuePID of the Parent Process
FunctionalityGet the PID of the Current Process’s Parent

fork()

Projectfork Function Description
Header File#include <unistd.h>
Functionpid_t fork(void);
Return ValueOn successful call,the parent process returns the child’s PID, and the child process returns 0
Returns -1 on failure
FunctionSystem call that creates a child process almost identical to the parent process, serving as one of the fundamental methods for achieving process concurrency

exec

ItemDescription
Functionint execve(const char *filename, char *const argv[], char *const envp[]);
Header file#include <unistd.h>
ParameterfilenamePathname of the new program, specifying the location of the program to be loaded into the process space
Parameterargv []Command-line argument array,argv[0]the first element is the command name, subsequent elements are arguments
Parameterenvp []Environment variable array for the new program
Return valueDoes not return on success; on failure, returns -1, and the error can be checked viaerrnocheck the error cause

The following functions are all implemented based on execve

1
2
3
4
5
6
7
8
9
10
11
12
13
int execl(const char *path, const char *arg, .../* (char
*) NULL */);

int execlp(const char *file, const char *arg, .../* (char
*) NULL */);

int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);

int execv(const char *path, char *const argv[])

int execvp(const char *file, char *const argv[]);

int execvpe(const char *file, char *const argv[],char *const envp[]);
Function prototypeCore differences (path / arguments / environment variables)Header fileReturn value
int execl(const char *path, const char *arg, .../* (char *) NULL */);Absolute path, variable arguments, inherits environment#include <unistd.h>Does not return on success, returns -1 on failure
int execlp(const char *file, const char *arg, .../* (char *) NULL */);Automatically searches PATH, variable arguments, inherits environment#include <unistd.h>Does not return on success, returns -1 on failure
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);Absolute path, variable arguments, custom environment#include <unistd.h>Does not return on success, returns -1 on failure
int execv(const char *path, char *const argv[]);Absolute path, argument array, inherits environment#include <unistd.h>Does not return on success, returns -1 on failure
int execvp(const char *file, char *const argv[]);Auto search PATH, argument array, inherit environment#include <unistd.h>Returns nothing on success, returns -1 on failure
int execvpe(const char *file, char *const argv[], char *const envp[]);Auto search PATH, argument array, custom environment#include <unistd.h>Returns nothing on success, returns -1 on failure
  1. Path rules: withpfunctions (execlp, execvp, execvpe) can accept a filename, automatically searching in the systemPATHdirectories specified by environment variables for the program; withoutpfunctions (execl, execle, execv) require the absolute path of the program (e.g.,/bin/ls)。
  2. Argument passing: withlfunctions (execl, execlp, execle) use variable arguments (...) to pass command-line arguments, and must end with(char*)NULL; withvfunctions (execv, execvp, execvpe) use a string arrayargvWhen passing parameters, the end of the array must beNULL
  3. environment variable: withefunctions (execle, execvpe) require manually passing an environment variable arrayenvp, customizing the runtime environment of the new program; withoutefunctions directly inherit the environment variables of the current process.

Example:

1
2
3
4
5
6
if (pid == 0)
{
printf("This is child,child pid is %d\n", getpid(), getppid());
execl("/home/test/hello","hello",NULL);
exit(1);//exit(1) will not execute (unless the execl() call fails)
}

pscommand

**Function:**List processes currently running in the system and their status.

Common parameters

ParameterMeaning
auxDisplay all processes of all users (a: show processes other than terminal, u: show user information, x: show processes without a controlling terminal)
-efSimilar to aux, display full format information
-eDisplay all processes
-fFull format display

Example:

1
$ ps aux

Output includes fields:

FieldMeaning
USERUser owning the process
PIDProcess ID
%CPUCPU usage
%MEMMemory usage
VSZVirtual memory size (KB)
RSSResident memory size (KB)
STATProcess status
COMMANDCommand name and arguments

Process Status STAT

CharacterDescription
DUninterruptible sleep (usually waiting for I/O)
RRunning or runnable (executing on CPU or waiting to be scheduled)
SSleeping (interruptible sleep, waiting for an event)
TStopped or traced (e.g.,Ctrl+Zstopped or being debugged)
ZZombie (child process terminated, but parent has not reaped it)
WOut of memory, cannot page (rare)

Additional Modifiers

CharacterDescription
<High-priority process
NLow-priority process
LLocked in memory

killCommand

**Function:**Send a signal to a process, usually to terminate it.

Example:

1
2
$ kill -l
$ kill -9 某个进程的pid

Common Signals

SignalDescription
SIGTERM(15)Terminate process, allowing cleanup; default signal
SIGKILL(9)Forcefully terminate process; cannot be caught, blocked, or ignored
SIGSTOP(19)Suspend process; can beSIGCONTResumed
SIGCONTContinue execution of a suspended process

Orphan Process

Orphan ProcessAfter the parent process ends, the child process has not yet ended, this child process is called an orphan process.

The orphan process will be adopted by the init process(in Ubuntu, it is /sbin/upstart), because some resources of the child process must be released by the parent process

Zombie process

Zombie processAfter the child process ends, the parent process is still running, but the parent process does not release the process control block, this child process is called a zombie process

wait

The wait() function is generally used in the parent process to wait for and reclaim the resources of the child process, thereby preventing the generation of zombie processes.

ItemDescription
Functionpid_t wait(int *status)
Header file#include <sys/wait.h>
Return valueOn success, returns the PID of the recycled child process; on failure, returns -1
Function and Interpretationwaitis a system call in Unix/Linux systems used by a parent process to wait for a child process to terminate. It blocks the parent process until a child process exits, then recycles the child’s resources and returns its PID.statusThe pointer is used to store the exit status of the child process, which can be parsed viaWIFEXITEDWEXITSTATUSmacros such as to determine whether the child process exited normally or terminated abnormally, and the specific exit code. This function is a key tool for process synchronization and resource reclamation, commonly used in scenarios where the parent process needs to wait for the child process to complete its task before continuing, such as in multi-process programming where the parent process uniformly manages the lifecycle of child processes.

Two macro definitions related to the parameters of the wait function:
WIFEXITED(status): Ifthe child process exits normally, this macro evaluates to true
WEXITSTATUS(status): Ifthe child process exits normally, the value of this macro is the exit value of the child process

Daemon

A daemon is a special type of process that runs in the background to perform specific system tasksMany daemons start at system boot and run until the system shuts down. Others start only when needed and automatically terminate after completing their tasks.

Daemons have no controlling terminal, so when certain events occur, whether they are routine informational reports or urgent messages requiring administrator attention, they need to be output in some way. The syslog function is the standard method for outputting such information; itsends the information to the syslogd daemon

Basic Requirements of a Daemon

  1. Must become a child of the init process (making the child process an orphan)
  2. Do not interact with the controlling terminal

Manually create a daemon process

Steps:

  1. Firstfork(): Detach from parent process
1
2
pid = fork();
if (pid > 0) exit(EXIT_SUCCESS); // Parent process exits
  • Purpose: Allow the child process to run in the background, so the parent process can immediately return to the shell.
  • The child process is no longer the process group leader (in preparation for the next stepsetsid).
  1. Callsetsid(): Create a new session
1
setsid(); // Become the session leader of the new session and the leader of the new process group
  • Effect
    • Detach from the controlling terminal;
    • No longer receive signals from the terminal (such asCtrl+CSIGHUP);
    • Become a newsession leaderandprocess group leader

⚠️ Must ensure that the process callingsetsid()isnot a process group leader, so the first stepfork()is necessary.

  1. Secondfork(): Ensure that the terminal cannot be reopened
1
2
pid = fork();
if (pid > 0) exit(EXIT_SUCCESS); // Intermediate process exits
  • Purpose: The newly forked child processis no longer the session leader
  • According to POSIX regulations,only the session leader can reapply for a controlling terminal
  • Therefore, after the secondfork()the processcan never open a tty, completely becoming an “orphan”.
  1. Change working directory to root directory/
1
chdir("/");
  • Reason: To prevent the daemon from occupying a mount point (such as/home), causing the file system to be unmountable (umountfailure).

Optional: You can also switch to a specific log or data directory, but ensure that directory will not be unmounted.

  1. Close and redirect standard file descriptors (0, 1, 2)
1
2
3
4
5
6
7
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

open("/dev/null", O_RDONLY); // stdin → fd 0
open("/dev/null", O_WRONLY); // stdout → fd 1
open("/dev/null", O_WRONLY); // stderr → fd 2
  • Reason
    • Inherited from the parent processstdin/stdout/stderrmay point to a terminal or pipe;
    • If not closed, it may cause resource leaks or unexpected output;
    • Redirect to/dev/nullcan avoid errors when library functions write (e.g.,printfwill not crash).
  1. Set file permission maskumask(0)
1
umask(0);
  • Effect: Clear the file creation mask, so that the permissions of subsequently created files/directories are entirely specified by the program (e.g.,06440755)。
  • facilitates precise control over permissions of logs, PID files, etc.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <syslog.h>

volatile sig_atomic_t running = 1;

void handle_sigterm(int sig) {
running = 0;
}

int main()
{
pid_t pid;

// 1. Create a child process
pid = fork();

if (pid < 0) {
perror("create pid error");
exit(-1);
} else if (pid > 0) { // pid > 0, parent process
exit(EXIT_SUCCESS);
}

// 2. The child process calls setsid to create a new session and become the leader of this new session,
// and also the process group leader of this session's process group
if (setsid() == -1) {
perror("daemon setsid error");
exit(EXIT_FAILURE);
}

// 4. Terminate the current process by creating a child process again
// Prevent the process from reopening the controlling terminal by making it no longer the session leader
pid = fork();
if (pid < 0) {
perror("create pid error");
exit(-1);
} else if (pid > 0) { // pid > 0, parent process
exit(EXIT_SUCCESS);
}

// 3. Change to the root directory to prevent occupying the mount directory
if(chdir("/") == -1){
perror("chdir to / error");
}

// 4. Close file descriptors: standard input, standard output, standard error
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

// 5. Redirect stdin, stdout, stderr
open("/dev/null", O_RDONLY); // stdin → fd 0
open("/dev/null", O_WRONLY); // stdout → fd 1
open("/dev/null", O_WRONLY); // stderr → fd 2

// 6. Set the umask file mask (open all permissions)
umask(0);

// daemon process
openlog("mydaemon", LOG_PID | LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "Daemon started");

// Register termination signal handling
signal(SIGTERM, handle_sigterm);
signal(SIGQUIT, handle_sigterm);

while(running) {
int fd;
time_t t;
char *buf;

fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1) {
syslog(LOG_ERR, "Failed to open /tmp/daemon.log file");
exit(EXIT_FAILURE);
}
t = time(0);
buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(2);
}
syslog(LOG_INFO, "Daemon stopped gracefully");
closelog();

return 0;
}

Directly call daemon()

You can also directly call the daemon function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

volatile sig_atomic_t running = 1;

void handle_term(int sig)
{
running = 0;
}

int main()
{
signal(SIGTERM, handle_term);
signal(SIGQUIT, handle_term);

if (daemon(0, 0) == -1) {
perror("daemon error\n");
exit(EXIT_FAILURE);
}
openlog("daemon_log", LOG_PID | LOG_CONS, LOG_DAEMON);

syslog(LOG_INFO, "daemon started via daemon()");

while (running) {
time_t t = time(NULL);
struct tm *tm = localtime(&t);
if (tm) {
char *buf = asctime(tm);
syslog(LOG_INFO, "Current time: %s", buf);
}
sleep(2);
}

syslog(LOG_INFO, "Daemon stopped");
closelog();
return 0;
}

Inter-process communication

Inter-process communication is also widely used, such as data transfer between background processes and GUI interfaces, sending signals to shut down, terminating running programs with Ctrl+C, etc.

Anonymous pipe

Anonymous pipes are the oldest form of inter-process communication, with the following two characteristics:

  1. Can only be used for data exchange between related processes, such as parent-child processes, sibling processes, descendant processes; no file node is visible in the directory, and read/write file descriptors are stored in an int array.
  2. **Data can only be transmitted in one direction.**That is, after the pipe is created, one process can only perform read operations, and the other process can only perform write operations. The byte order read out is the same as the order written.
ItemDescription
Functionint pipe(int pipefd[2])
Header file#include <unistd.h>
Parameter pipefd [2]Integer array,pipefd[0]is the read-end file descriptor,pipefd[1]is the write-end file descriptor
Return valueReturns 0 on success, -1 on failure
Function and interpretationpipeis a system call in Unix/Linux systems used to create unnamed pipes. Unnamed pipes are a method of inter-process communication, suitable only for bidirectional communication between processes with a kinship relationship (such as parent and child processes). After creation, one process can write data through the write end (pipefd[1]) and another process can read data through the read end (pipefd[0]) Read data to achieve one-way data transmission (if bidirectional is needed, two pipes must be created). Commonly used for command passing and data sharing between parent and child processes, for example, the parent process sends instructions to the child process through a pipe, and the child process returns the result to the parent process through the pipe after execution.

Steps

  1. Call pipe() to create an unnamed pipe
  2. fork() creates a child process, one process reads using read(), and one process writes using write()

Note:The unnamed pipe must be created before the fork() function!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main()
{
pid_t pid;
int pipefd[2], ret;
// Create unnamed pipe
ret = pipe(pipefd);
if (ret == -1) {
perror("create pipe error");
}

pid = fork();

if (pid < 0) {
perror("fork error");
} else if (pid == 0) { // child
char buf[32] = { 0 };
close(pipefd[1]); // Close write end
read(pipefd[0], buf, 32);
close(pipefd[0]);
printf("buf is %s\n", buf);
printf("child process exit\n");
exit(EXIT_SUCCESS);

} else { // parent
int status;
char *s = "hello child process";

close(pipefd[0]); // Close read end
write(pipefd[1], s, strlen(s));
close(pipefd[1]);

wait(&status);
printf("parent process exit\n");
exit(EXIT_SUCCESS);
}

return 0; // unreachable
}

Output:

1
2
3
4
5
6
$ make
gcc -g -Wall main.c -o mypipe
$ ./mypipe
buf is hello child process
child process exit
parent process exit

Named pipe

ItemDescription
Functionint mkfifo(const char *pathname, mode_t mode)
Header file#include <sys/types.h>
#include <sys/stat.h>
ParameterspathnameThe path and name of the named pipe, used to identify the pipe file to be created
ParametersmodePermission mask, setting read and write permissions for user and group in octal (e.g.,0666
Return ValueReturns 0 on success, -1 on failure
Function and ExplanationmkfifoIt is a function in Unix/Linux systems used to create named pipes. A named pipe is a special file that can be used for inter-process communication between unrelated processes. After creation, one process can write data to it like a regular file, and another process can read data from it, enabling bidirectional communication between processes. It is commonly used in scenarios where different programs exchange information, such as a server program and a client program passing commands and data through a named pipe. When using it, pay attention to permission settings to ensure that the communicating processes have appropriate read and write permissions for the pipe file.

Usage Steps

  1. Use mkfifo() to create a FIFO file descriptor.
  2. Open the pipe file descriptor.
  3. Perform unidirectional data transfer by reading and writing the file descriptor

Example

fifo_write.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int ret;
char buf[32] = {0};
int fd;
if (argc < 2){
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
if (access(argv[1], F_OK) == 0){
ret = mkfifo(argv[1], 0666);
if (ret == -1){
printf("mkfifo is error \n");
return -2;
}
printf("mkfifo is ok \n");
}
fd = open(argv[1], O_WRONLY);
while (1){
sleep(1);
write(fd, "hello", 5);
}
close(fd);
return 0;
}

fifo_read.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
int fd;
if (argc < 2){
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDONLY);
while (1){
sleep(1);
read(fd, buf, 32);
printf("buf is %s\n", buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
return 0;
}

It can also be created using a command

1
2
3
mkfifo fifo
ls
ls -al

The pipe type file has a size of 0 and does not occupy disk space

Signal Communication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[zhaohang@cyberboy /]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Signal NameDescriptionDefault Action
SIGHUPTerminal hangup or controlling terminal closed (user logout)Terminate
SIGINTTerminal interrupt (Ctrl+C)Terminate
SIGQUITTerminal quit (Ctrl+)Terminate + core dump
SIGILLIllegal instructionTerminate + core dump
SIGTRAPDebug breakpoint, single-step interruptTerminate + core dump
SIGABRTabort()Call traceTerminate + core dump
SIGBUSBus error (misaligned address access)Terminate + core dump
SIGFPEArithmetic exception (e.g., division by zero)Terminate + core dump
SIGKILLForced termination (cannot be caught/ignored)Terminate
SIGUSR1User-defined signal 1Terminate
SIGSEGVSegmentation fault (invalid memory access)Terminate + core dump
SIGUSR2User-defined signal 2Terminate
SIGPIPEWrite to pipe with no readerTerminate
SIGALRMalarm()ExpirationTermination
SIGTERMTermination signal (catchable)Terminate
SIGCHLDChild process exited or stoppedIgnore
SIGCONTContinue running stopped processContinue execution
SIGSTOPStop process (uncatchable)Stop
SIGTSTPUser pause (Ctrl+Z)Stop
SIGTTINBackground process attempting read from terminalStop
SIGTTOUBackground process attempting write to terminalStop
SIGURGSocket urgent dataIgnore
SIGXCPUCPU time limit exceededTerminate + stack dump
SIGXFSZFile size limit exceededTerminate + stack dump
SIGVTALRMVirtual timer expired (ITIMER_VIRTUALTerminate
SIGPROFProfiling timer expired (ITIMER_PROFTerminate
SIGWINCHTerminal window size changedIgnore
SIGIO / SIGPOLLAsynchronous I/O event (device readable/writable)Ignore
SIGSYSIllegal system callTerminate + core dump
SIGPWRPower failure (some system implementations)Ignore or terminate
SIGSTKFLTCoprocessor stack error (rarely used)Terminate

Send signal

kill()
ItemDescription
Functionint kill(pid_t pid, int sig)
Header file#include <sys/types.h>
#include <signal.h>
Parameter pid- Greater than 0: send signal to process with PIDpidsend signal to the process
- Equal to 0: send signal to processes in the same process group
- Equal to -1: Send signal to all processes with process ID greater than 1, except itself
- Less than -1: Send signal to all processes in the process group whose group ID equals the absolute value ofpidthe absolute value
Parameter sigThe signal to send; when equal to 0, it is a null signal, commonly used for error checking
Return valueReturns 0 on success, -1 on error and setserrno
Function and interpretationkillis a function in Unix/Linux systems used to send signals to processes or process groups. Signals are a lightweight method of inter-process communication, used for operations such as interrupting, terminating, or querying the status of processes. For example, sendingSIGTERMsignal can gracefully terminate a process, sendingSIGKILLsignal can forcefully terminate a process. It is widely used in process management, program debugging, and other scenarios, serving as a key tool for asynchronous notification between processes.
raise()
ItemDescription
Functionint raise(int sig)
Header file#include <signal.h>
Parameter sigSignal to be sent
Function and interpretationraiseThe function is used to send a signal to the process itself, equivalent tokill(getpid(), sig). Signals are a mechanism for asynchronous communication between processes (including itself). This function can be used to implement logic such as self-interruption or self-termination of a process. For example, when a program detects an internal error, it usesraise(SIGABRT)to actively trigger an abnormal termination, facilitating debugging and resource cleanup.
alarm()
ItemDescription
Functionunsigned int alarm(unsigned int seconds)
Header file#include <unistd.h>
ParameterSet alarm time (in seconds)
FunctionAfter the set time expires, send theSIGALRMsignal to the process, with the default action being to terminate the process
NoteEach process can only have one activealarmtimer. If you need to use it again, you must re-register it

Receiving Signals

Receiving Signals: If we want theprocess receiving the signal to be able to receive it, then this process must not stop. There are three methods to keep the process from stopping:

  • while
  • sleep
  • pause
pause()
ItemDescription
Functionint pause(void)
Header File#include <unistd.h>
Return ValueReturns -1 after the process is interrupted by a signal
Function and InterpretationpauseThe function is used to suspend the process, putting it into a sleep state until a signal is received. It is commonly used in scenarios where a process needs to wait for an external event (such as a signal trigger) to continue execution, for example, implementing simple process synchronization or waiting for a specific signal to handle asynchronous events. Note that if the signal’s handling action is to terminate the process or ignore the signal,pausewill not return; only after the signal handler finishes execution,pausewill it return -1, at which point you can useerrnofor further analysis (usuallyerrnoisEINTRindicating interruption by a signal).

Signal Handling

Signals are handled by the operating systemthat is, signal processing occurs in kernel mode. Signals may not be processed immediately; at this point, they are stored in the signal table

Signal Processing
Signal Processing

From the above figure, it can be seen that there are three ways to handle signals:

  1. Default method (usually terminating the process),
  2. Ignore, performing no operation.
  3. Catch and handle by calling a signal handler (in the form of a callback function)
signal()
ProjectDescription
Functionsighandler_t signal(int signum, sighandler_t handler);(can be simplified tosignal(参数1, 参数2);
Header file#include <unistd.h>
Parameter 1 (signum)The signal to be handled. System signals can be viewed via the terminal commandkill -lview
Parameter 2 (handler)Signal handling method:
- Ignore signal: fill inSIG_IGN
- System default handling: fill inSIG_DFL
- Catch the signal and execute a custom function: must pass a function that conforms tosighandler_tfunction pointer of type (function definition format isvoid 函数名(int)
return valueOn success, returns the previous value of the signal handler registered for this signalhandlervalue; on failure, returnsSIG_ERR
Function and ExplanationsignalThe function is used to modify the action taken by a process upon receiving a signal. It is a fundamental tool for signal handling in Unix/Linux systems. It allows ignoring, default handling, or custom capture logic for signals, such as capturing theSIGINTsignal (terminal interrupt signal) to achieve graceful program exit. It should be noted thatsignalbehavior differs across systems; for portability, it is recommended to use thesigactionfunction.

Example

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void)
{
signal(SIGINT,SIG_IGN);
while(1){
printf("wait signal\n");
sleep(1);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void myfun(int sig)
{
if(sig == SIGINT){
printf("get sigint\n");
}
}

int main(void)
{
signal(SIGINT,myfun);
while(1){
sleep(1);
printf("wait signal\n");
}
return 0;
}

Shared Memory

Shared memory, as the name implies, allows two unrelated processes to access the same logical memory. It is a very efficient way for two running processes to share and transfer data. The memory shared between different processes is typically the same physical memory. Processes can attach the same physical memory to their own address spaces, and all processes can access the addresses in the shared memory. If one process writes data to the shared memory, the changes immediately affect any other process that can access the same shared memory.

Features

  1. Fast speed, because shared memory does not require kernel control, so there are no system calls. Additionally, there is no process of copying data to the kernel, making it the fastest among the previous methods. It can be used for batch data transfer, such as images.
  2. No synchronization mechanism; it requires other tools provided by Linux for synchronization, typically using semaphores.

Steps to use shared memory

  1. Call shmget() to create a shared memory segment ID,
  2. Call shmat() to attach the shared memory segment identified by the ID to the process’s virtual address space,
  3. Access the mapped address space added to the process, which can be read and written using I/O operations.

shmget()

Create or get shared memory

ItemDescription
Functionint shmget(key_t key, size_t size, int shmflg)
Parameter keyGenerated byftokThe generated key identifier, used to uniquely identify IPC resources (shared memory) in the system
Parameter sizeThe size of the requested shared memory. The minimum unit for memory allocation by the operating system is a page (4k bytes), and it will be aligned upward to the page size.
Parameter shmflgControl flags:
- To create new shared memory: must combine withIPC_CREATandIPC_EXCL
- To use existing shared memory: can useIPC_CREATor pass 0 directly
Return ValueReturns the shared memory identifier on success; returns -1 and sets an error code on failure
Function and Interpretationshmgetis a system call in Unix/Linux systems used to create or obtain shared memory. Shared memory is the most efficient method of inter-process communication, allowing multiple processes to share data by mapping the same block of shared memory.shmgetis responsible for initializing the creation of shared memory or locating existing shared memory, providing the foundation for subsequentshmat(mapping),shmdt(unmapping),shmctl(control) operations. For example, when multiple processes collaborate to process large data, high-speed data transfer between processes can be achieved through shared memory.

key == IPC_PRIVATE

  • always creates a brand new, private shared memory segment
  • This segmentcan only be accessed by the current process and its child processes (inherited via fork)
  • Other unrelated processesCannot find it via key(becauseIPC_PRIVATEis not a global identifier);
  • Commonly used forinter-process communication between parent and child

key != IPC_PRIVATE(i.e., using a “global” key, such asftok()generated)

Whether a new segment is created at this point depends on whether two conditions aresimultaneously satisfied

  1. does not yet exist in the systemthe shared memory segment corresponding tokey;
  2. shmflgcontains theIPC_CREATflag.

If both conditions are met →Create a new segment

If the segment already exists →Return the ID of the existing segment(will not overwrite);

If not addedIPC_CREATand the segment does not exist → return -1, errorENOENT

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
int shmid;
shmid = shmget(IPC_PRIVATE, 1024, 0777);
if (shmid < 0){
printf("shmget is error\n");
return -1;
}
printf("shmget is ok and shmid is %d\n", shmid);
return 0;
}

If created using IPC_PRIVATE, the key value is 0

View shared memory segments via command:

1
2
3
4
5
6
7
8
$ ipcs -m

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status


# Delete
$ ipcrm -m 0

-mindicates the operationshared memory segment, the following0isshmid(shared memory ID).

ftok()

Generate a unique key identifier for IPC (Inter-Process Communication) resources

ItemDescription
Functionkey_t ftok(const char *pathname, int proj_id)
Header file#include <sys/types.h>
#include <sys/ipc.h>
ParameterpathnamePath and name of the file, used as the base identifier for generating the unique key
Parameterproj_idCharacter identifier (usually a non-zero single-byte value), used to distinguish different keys generated from the same file
Return valueOn success, returnskeyvalue; on failure, returns -1
Function and interpretationftokThis function is used to generate a unique key identifier for IPC (Inter-Process Communication) resources, serving as the foundation for IPC mechanisms such as message queues, shared memory, and semaphores. It combines the file’s inode information withproj_idto generate a uniquekey, ensuring that different processes can use thiskeyto identify and access the same IPC resource. For example, when multiple processes use shared memory, they must first useftokto generate a consistentkey, then callshmgetto create or obtain shared memory. When using it, note that if the file path orproj_idchanges, the generatedkeywill also change, which may prevent processes from recognizing the shared resource.

shmat()

Attach shared memory to the process’s address space

ItemDescription
Functionvoid *shmat(int shmid, const void *shmaddr, int shmflg)
Header file#include <sys/types.h>
#include <sys/shm.h>
Parameterint shmidShared memory identifier, i.e., theshmgetreturn value of the function, used to identify the shared memory to be attached
Parameterconst void *shmaddrMapping address, usually writeNULL, the system automatically completes the mapping of shared memory to the process address space
Parameterint shmflgaccess permission flag,0indicates readable and writable,SHM_RDONLYindicates read-only
Return ValueOn success, returns the address of the shared memory mapped into the process; on failure, returns -1 ((void*)-1
Function and InterpretationshmatThe function is used to attach shared memory to the process’s address space, a key step in the shared memory usage process. After the process obtains the virtual address of the shared memory through this function, it can read and write the shared area like normal memory, achieving efficient data sharing between processes. For example, in multi-process collaborative tasks, data written by one process to shared memory can be read by other processes that have attached the same shared memory. When using it, note that after successful attachment, it must beshmdtto unmap, avoiding resource leaks; also pay attention to access permissions to ensure the process’s operations on shared memory comply withshmflgsettings.

shmdt()

Removes the mapping of shared memory from the process address space, but does not delete the shared memory object in the kernel

ProjectDescription
Functionint shmdt(const void *shmaddr)
Header File#include <sys/types.h>
#include <sys/shm.h>
Parametersconst void *shmaddrAddress of shared memory mapped to the process (i.e.,shmatthe return value of the function)
Return ValueReturns 0 on success, -1 on failure
FunctionalityUnmaps the shared memory from the process address space (detaches shared memory)
NoteshmdtOnly removes the mapping of shared memory from the process address space, does not delete the shared memory object in the kernel. To delete the shared memory in the kernel, call theshmctlfunction and specifyIPC_RMIDcommand.

shmctl()

Performs control operations on shared memory, using differentcmdcommand, can achievequery, modification of shared memory attributes, and deletion of objects

ItemDescription
Functionint shmctl(int shmid, int cmd, struct shmid_ds *buf)
Header file#include <sys/ipc.h>
#include <sys/shm.h>
Parameterint shmidShared memory identifier, used to specify the shared memory resource to operate on
Parameterint cmdOperation command:
-IPC_STAT: Get shared memory attributes
-IPC_SET: Set shared memory attributes
-IPC_RMID: Delete shared memory object
Parameterstruct shmid_ds *bufPointer to the structure used to store or set shared memory attributes, inIPC_STATandIPC_SETwhen used
Function and InterpretationshmctlThe function is used to control shared memory operations and is a key tool for managing the lifecycle of shared memory. Through differentcmdcommands, it can query, modify attributes, and delete objects of shared memory. For example, when a process no longer needs shared memory, callingshmctl(shmid, IPC_RMID, NULL)can delete the shared memory object in the kernel, releasing system resources. This function ensures proper reclamation of shared memory resources, avoiding system overhead or conflicts caused by unreleased resources.

Example

Parent and child processes communicate via shared memory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
int shmid;
pid_t pid;

// IPC_PRIVATE is only accessed by the parent and child processes
shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | IPC_EXCL | 0600);

if (shmid < 0) {
perror("create share memory error");
return -1;
}
printf("shmget is ok\n");

pid = fork();
if (pid < 0) {
fprintf(stderr, "Error %s(errno: %d)\n", strerror(errno), errno);
perror("fork error");
// Delete the shared memory segment
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
fprintf(stderr, "Error %s(errno: %d)\n", strerror(errno), errno);
perror("shmctl IPC_RMID");
return 1;
}
exit(EXIT_FAILURE);
} else if (pid == 0) {
void *addr = shmat(shmid, NULL, SHM_RDONLY);
if (addr == (void *)-1) {
fprintf(stderr, "Error %s(errno: %d)\n", strerror(errno), errno);
perror("shmat(child) error");
exit(EXIT_FAILURE);
}
printf("Child read: %s\n", (char *)addr);

shmdt(addr); // Explicit detach (not required, exit will automatically detach)
exit(EXIT_SUCCESS);
} else {
int status;
void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) {
fprintf(stderr, "Error %s(errno: %d)\n", strerror(errno), errno);
perror("shmat(parent) error");
// Delete the shared memory segment
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID");
return 1;
}
exit(EXIT_FAILURE);
}

strncpy((char *)addr, "hello", 6);
wait(&status);

shmdt(addr);
// Delete the shared memory segment
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID");
return 1;
}

exit(EXIT_SUCCESS);
}

return 0;
}

There is no guarantee here that the parent process has finished writing when the child process reads the shared memory, so a pipe or semaphore is needed

Message Queue

Three types of IPCs

1
2
3
4
5
6
7
8
9
10
[zhaohang@cyberboy /]$ ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status

------ Semaphore Arrays --------
key semid owner perms nsems

IPC objects are stored in kernel space
IPC objects are stored in kernel space

The steps for application layer IPC communication are

  1. Obtain a key value; the kernel maps the key to an IPC identifier. Common methods to obtain a key:

    • Use the IPC_PRIVATE constant as the key value in the get call.
    • Use ftok() to generate a key.
  2. Execute the IPC get call to obtain an integer IPC identifier id from the key; each id represents an IPC object.

    • Message queue: msgget()
    • Shared memory: shmget()
    • Semaphore: semget()
  3. Access the IPC object via its id.

Characteristics of message queues

  1. Sent messages are stored in a linked list, akin to a list; processes can add and retrieve messages from the corresponding ‘list’ by id.
  2. When receiving data, a process can retrieve data from the queue by type.

Steps for using message queues

  1. Create key;
  2. msgget() creates (or opens) the message queue object id via key;
  3. Use msgsnd()/msgrcv() for sending and receiving;
  4. Delete the IPC object via msgctl();

After obtaining the id via msgget() call, the message queue can be used to access the IPC object. Common APIs for message queues are as follows:

msgget()

Used to obtain or create the unique identifier ID of the message queue

Functionmsgget
Function Prototypeint msgget(key_t key, int msgflg)
Header File#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
Parameters-key: Key value associated with the message queue
-msgflg: Access permissions: -IPC_CREAT: Create if the message queue does not exist -IPC_EXCL: Used withIPC_CREATtogether, if the message queue already exists, an error is reported - permission bits (e.g.,0666): Specifies the access permissions for the created message queue.
Return ValueReturns the message queue ID on success, or -1 on failure.
Function and ExplanationUsed to obtain or create a unique identifier for a message queue, which is the initialization step of the message queue IPC mechanism.

msgsnd()

Functionmsgsnd
Function Prototypeint msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
Header File#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
Parameters-msqid: Message queue ID
-msgp: Pointer to the message
-msgsz: Number of bytes in the message
-msgflg: Send mode (0 for blocking,IPC_NOWAITnon-blocking)
Return ValueReturns 0 on success, -1 on failure
Function and ExplanationUsed to send data to a message queue, supporting blocking/non-blocking modes. It is a core tool for inter-process message passing.

msgctl()

Functionmsgctl
Function Prototypeint msgctl(int msqid, int cmd, struct msqid_ds *buf)
Header File#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
Parameters-msqid: Message queue ID
-cmd: Operation command (IPC_STAT/IPC_SET/IPC_RMID
-buf: Pointer to a structure for storing or setting attributes
Return ValueReturns 0 on success, -1 on failure
Function and ExplanationUsed to query, modify, or delete attributes of a message queue. It is a key function for managing the lifecycle of a message queue.

msgrcv()

ProjectDescription
Functionssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
ParametermsqidIPC identifier of the message queue, used to specify the queue from which to receive messages
ParametermsgpPointer to the message buffer, used to store the received message (the message must include type and data fields)
ParametermsgszSize of the message data field (excluding the bytes of the message type)
ParametermsgtypType of message to receive, used to filter messages of a specific type
ParametermsgflgBitmask that can combine multiple flags (e.g.,IPC_NOWAITindicates non-blocking reception)
Return ValueOn success, returns the size of the received message data field; on error, returns -1
Function and Interpretationmsgrcvis a function in Unix/Linux systems used to receive messages from a message queue. It supports filtering reception by message type, enabling asynchronous message interaction and categorized processing between processes. For example, in scenarios where multiple clients send different types of requests to a server, the server can usemsgtypto distinguish request types and process them separately. The reception mode can bemsgflgconfigured as blocking or non-blocking, flexibly adapting to different business needs. This function is a key part of implementing message consumption in the message queue IPC mechanism.

Example

Write to the message queue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf {
long mtype;
char mtext[128];
};

int main(int argc, char **argv)
{
key_t key;
int msgid, ret;
struct msgbuf msg = {
.mtype = 1, // greater than 0
.mtext = "hello world!",
};

key = ftok("./a.c", 'a');
if (key == -1) {
fprintf(stderr, "Error: %s(errno: %d)\n", strerror(errno), errno);
perror("ftok");
exit(EXIT_FAILURE);
}

msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
fprintf(stderr, "Error: %s(errno: %d)\n", strerror(errno), errno);
perror("msgget");
exit(EXIT_FAILURE);
}

// Blocking mode
ret = msgsnd(msgid, (void *)&msg, strlen(msg.mtext)+1, 0);
if (ret == -1) {
fprintf(stderr, "Error: %s(errno: %d)\n", strerror(errno), errno);
perror("msgsnd");
exit(EXIT_FAILURE);
}

printf("msgsnd ok\n");

// remove after receive
// if (msgctl(msgid, IPC_RMID, NULL) == -1) {
// fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
// perror("msgctl IPC_RMID");
// }

return 0;
}

Read from the message queue from the beginning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf {
long mtype;
char mtext[128];
};

int main(int argc, char **argv)
{
int msgid;
key_t key;
struct msgbuf msg;
long mtype = 0;

key = ftok("./a.c", 'a');
if (key == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("ftok error");
exit(EXIT_FAILURE);
}

msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("msgget error");
exit(EXIT_FAILURE);
}
printf("msget is ok and msgid is %d\n", msgid);

// Non-blocking mode
if (msgrcv(msgid, (void *)&msg, sizeof(msg.mtext), mtype, MSG_NOERROR | IPC_NOWAIT) == -1) {
if (errno != ENOMSG) {
perror("msgrcv");
if (msgctl(msgid, IPC_RMID, NULL) == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("msgctl IPC_RMID");
}
exit(EXIT_FAILURE);
}
printf("No message available for msgrcv()\n");
} else {
printf("message recieved: %s\n", msg.mtext);
}

if (msgctl(msgid, IPC_RMID, NULL) == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("msgctl IPC_RMID");
}
return 0;
}

Run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./msg_write.o
msgsnd ok
$ ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages
0x613012a9 2 zhaohang 666 13 1

$ ./msg_read.o
msget is ok and msgid is 2
message recieved: hello world!
$ ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages

Semaphore

To prevent a series of problems caused by multiple programs accessing a shared resource simultaneously, we need a method that can authorize access by generating and using tokens, allowing only one execution thread to access the critical section of code at any given time. A critical section refers to code that performs data updates and needs to be executed exclusively. Semaphores provide such an access mechanism, ensuring that only one thread accesses a critical section at a time. In other words, semaphores are used to coordinate process access to shared resources.

Semaphores can only perform two operations: wait and signal, i.e.,P(sv)andV(sv),

  • P(sv): If the value of sv is greater than zero, decrement it by 1; if its value is zero, suspend the execution of the process.

  • V(sv): If there are other processes suspended waiting for sv, resume one; if no process is suspended waiting for sv, increment it by 1.

semget()

Functionsemget
Function Prototypeint semget(key_t key, int nsems, int semflg)
Header File#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
Parameters-key: Key value of the semaphore
-nsems: Number of semaphores
-semflg: Flags (e.g., creation/access permissions)
Return ValueReturns semaphore ID on success, -1 on failure
Function and ExplanationUsed to create a new semaphore or obtain the ID of an existing semaphore; it is the initialization step of the semaphore IPC mechanism.

semctl()

Functionsemctl
Function Prototypeint semctl(int semid, int semnum, int cmd, union semun arg)
Header File#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
Parameters-semid: Semaphore ID -semnum: Semaphore number
-cmd: Operation command (IPC_STAT/IPC_SET/IPC_RMID/SETVALetc.)
-argunion semunStructure for storing or setting semaphore attributes
Function and InterpretationUsed to query, modify, initialize, or delete semaphore attributes; it is a key function for semaphore lifecycle management.

semop()

Functionsemop
Function prototypeint semop(int semid, struct sembuf *sops, size_t nsops)
Header file#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
Parameters-semid: Semaphore ID
-sopsstruct sembufArray of structures, each element contains semaphore number, operation (sem_op: 1 for V operation, -1 for P operation, 0 for wait), operation mode (sem_flg: 0 means blocking,IPC_NOWAITnon-blocking)
-nsops: number of semaphores to operate on
Function and InterpretationUsed to perform P (acquire resource) and V (release resource) operations on semaphores, enabling synchronization and mutual exclusion between processes.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <unistd.h>

union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};

void do_something_exclusively()
{
printf("pid[%d]: doing someting exclusively\n", getpid());
sleep(2);
}

int main(int argc, char **argv)
{
pid_t pid;
key_t key;
int semid, ret = 0;
union semun init_val = { .val = 1 };

key = ftok(".", 's');
if (key == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("ftok");
exit(EXIT_FAILURE);
}

// Create a semaphore set with one semaphore
semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
if (errno == EEXIST) {
// Already exists, directly obtain
semid = semget(key, 1, 0666);
if (semid == -1){
perror("semget an existing one failed");
exit(EXIT_FAILURE);
}

} else {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("semget");
exit(EXIT_FAILURE);
}
}
// Initialize semaphore to 1
ret = semctl(semid, 0, SETVAL, init_val);
if (ret == -1) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("semctl");
exit(EXIT_FAILURE);
}
printf("Before fork: Created semaphore (id=%d), value=1\n", semid);

pid = fork();
if (pid < 0) {
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child process
// P op
struct sembuf sops = {
.sem_num = 0,
.sem_op = -1,
.sem_flg = 0,
};
if (semop(semid, &sops, 1) == -1) {
perror("semop P");
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
exit(EXIT_FAILURE);
}

do_something_exclusively();

// V op
sops.sem_op = +1;
if (semop(semid, &sops, 1) == -1) {
perror("semop V");
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
exit(EXIT_FAILURE);
}

} else { // parent process

// P op
struct sembuf sops = {
.sem_num = 0,
.sem_op = -1,
.sem_flg = 0,
};
if (semop(semid, &sops, 1) == -1) {
perror("semop P");
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
exit(EXIT_FAILURE);
}

do_something_exclusively();

// V op
sops.sem_op = +1;
if (semop(semid, &sops, 1) == -1) {
perror("semop V");
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
exit(EXIT_FAILURE);
}

wait(NULL); // Wait for child process to finish;
if (semctl(semid, 0, IPC_RMID) == -1) { // Parent process deletes the semaphore set
fprintf(stderr, "Error: %s(errno: %d)", strerror(errno), errno);
perror("semctl IPC_RMID");
exit(EXIT_FAILURE);
}
}

return 0;
}