Skip to content

Commit 5bf942f

Browse files
register processes for SIGUSR1 by sending SIGUSR1 to modbus-tcp-client-shm
1 parent fa0f308 commit 5bf942f

File tree

9 files changed

+141
-55
lines changed

9 files changed

+141
-55
lines changed

.clang-tidy

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ Checks: 'bugprone-*,
5050
readability-static-definition-in-anonymous-namespace,
5151
readability-string-compare,
5252
readability-uniqueptr-delete-release,
53-
readability-use-anyofallof
54-
-modernize-use-trailing-return-type
55-
-bugprone-exception-escape'
53+
readability-use-anyofallof,
54+
-modernize-use-trailing-return-type,
55+
-bugprone-exception-escape,
56+
-clang-diagnostic-switch-default'
5657
WarningsAsErrors: '*,
5758
-modernize-*,
5859
-readability-*

.clang-tidy-noerrors

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ Checks: 'bugprone-*,
5050
readability-static-definition-in-anonymous-namespace,
5151
readability-string-compare,
5252
readability-uniqueptr-delete-release,
53-
readability-use-anyofallof
54-
-modernize-use-trailing-return-type
55-
-bugprone-exception-escape'
53+
readability-use-anyofallof,
54+
-modernize-use-trailing-return-type,
55+
-bugprone-exception-escape,
56+
-clang-diagnostic-switch-default'
5657
WarningsAsErrors: ''
5758
HeaderFilterRegex: ''

README.md

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,28 @@
33
Modbus tcp client that stores its data (registers) in shared memory objects.
44

55
## Dependencies
6+
67
- cxxopts by jarro2783 (https://github.com/jarro2783/cxxopts) (only required for building the application)
78
- libmodbus by Stéphane Raimbault (https://github.com/stephane/libmodbus)
89
- cxxshm (https://github.com/NikolasK-source/cxxshm)
910
- cxxsemaphore (https://github.com/NikolasK-source/cxxsemaphore)
1011

1112
On Arch linux they are available via the official repositories and the AUR:
13+
1214
- https://archlinux.org/packages/extra/any/cxxopts/
1315
- https://aur.archlinux.org/packages/libmodbus
1416
- https://aur.archlinux.org/packages/cxxshm
1517
- https://aur.archlinux.org/packages/cxxsemaphore
1618

1719
## Build
20+
1821
```
1922
cmake -B build -DCMAKE_CXX_COMPILER=$(which clang++) -DCMAKE_BUILD_TYPE=Release -DCLANG_FORMAT=OFF -DCLANG_TIDY=OFF -DCOMPILER_WARNINGS=OFF -DBUILD_DOC=OFF
2023
cmake --build .
2124
```
2225

2326
## Use
27+
2428
```
2529
modbus-tcp-client-shm [OPTION...]
2630
@@ -33,14 +37,21 @@ modbus-tcp-client-shm [OPTION...]
3337
3438
shared memory options:
3539
-n, --name-prefix arg shared memory name prefix (default: modbus_)
36-
--force Force the use of the shared memory even if it already exists. Do not use this option per default! It should only be used if the shared memory of an improperly terminated instance continues to exist as an orphan
37-
and is no longer used.
38-
-s, --separate arg Use a separate shared memory for requests with the specified client id. The client id (as hex value) is appended to the shared memory prefix (e.g. modbus_fc_DO). You can specify multiple client ids by
39-
separating them with ','. Use --separate-all to generate separate shared memories for all possible client ids.
40-
--separate-all like --separate, but for all client ids (creates 1028 shared memory files! check/set 'ulimit -n' before using this option.)
40+
--force Force the use of the shared memory even if it already exists.
41+
Do not use this option per default!
42+
It should only be used if the shared memory of an improperly terminated instance continues
43+
to exist as an orphan and is no longer used.
44+
-s, --separate arg Use a separate shared memory for requests with the specified client id.
45+
The client id (as hex value) is appended to the shared memory prefix (e.g. modbus_fc_DO).
46+
You can specify multiple client ids by separating them with ','.
47+
Use --separate-all to generate separate shared memories for all possible client ids.
48+
--separate-all like --separate, but for all client ids (creates 1028 shared memory files!
49+
check/set 'ulimit -n' before using this option.)
4150
--semaphore arg protect the shared memory with a named semaphore against simultaneous access
42-
--semaphore-force Force the use of the semaphore even if it already exists. Do not use this option per default! It should only be used if the semaphore of an improperly terminated instance continues to exist as an orphan and is
43-
no longer used.
51+
--semaphore-force Force the use of the semaphore even if it already exists.
52+
Do not use this option per default!
53+
It should only be used if the semaphore of an improperly terminated instance continues
54+
to exist as an orphan and is no longer used.
4455
-b, --permissions arg permission bits that are applied when creating a shared memory. (default: 0640)
4556
4657
modbus options:
@@ -49,9 +60,15 @@ modbus-tcp-client-shm [OPTION...]
4960
--ao-registers arg number of analog output registers (default: 65536)
5061
--ai-registers arg number of analog input registers (default: 65536)
5162
-m, --monitor output all incoming and outgoing packets to stdout
52-
--byte-timeout arg timeout interval in seconds between two consecutive bytes of the same message. In most cases it is sufficient to set the response timeout. Fractional values are possible.
53-
--response-timeout arg set the timeout interval in seconds used to wait for a response. When a byte timeout is set, if the elapsed time for the first byte of response is longer than the given timeout, a timeout is detected. When
54-
byte timeout is disabled, the full confirmation response must be received before expiration of the response timeout. Fractional values are possible.
63+
--byte-timeout arg timeout interval in seconds between two consecutive bytes of the same message.
64+
In most cases it is sufficient to set the response timeout.
65+
Fractional values are possible.
66+
--response-timeout arg set the timeout interval in seconds used to wait for a response.
67+
When a byte timeout is set, if the elapsed time for the first byte of response is
68+
longer than the given timeout, a timeout is detected.
69+
When byte timeout is disabled, the full confirmation response must be received
70+
before expiration of the response timeout.
71+
Fractional values are possible.
5572
5673
other options:
5774
-h, --help print usage
@@ -63,6 +80,11 @@ modbus-tcp-client-shm [OPTION...]
6380
--longversion print version (including compiler and system info) and exit
6481
--shortversion print version (only version string) and exit
6582
--git-hash print git hash
83+
84+
signal options:
85+
-k, --signal arg send SIGUSR1 to process on writing modbus commands
86+
--signal-register allow processes to register themselves for receiving SIGUSR1 on writing modbus commands
87+
by sending SIGUSR1.
6688
6789
6890
The modbus registers are mapped to shared memory objects:
@@ -75,10 +97,14 @@ The modbus registers are mapped to shared memory objects:
7597
```
7698

7799
### Use privileged ports
78-
The standard modbus port (502) can be used only by the root user under linux by default.
79-
To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a higher port.
100+
101+
The standard modbus port (502) can be used only by the root user under linux by default.
102+
To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a
103+
higher port.
80104
The following example redirects packets from port 502 (standard modbus port) to port 5020
105+
81106
```
82107
iptables -A PREROUTING -t nat -p tcp --dport 502 -j REDIRECT --to-port 5020
83108
```
109+
84110
The modbus client must be called with the option ```-p 5020```

cmake_files/warnings.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ function(clangwarn target)
6262
target_compile_options(${target} PUBLIC -Wno-nested-anon-types)
6363
target_compile_options(${target} PUBLIC -Wno-gnu-anonymous-struct)
6464
target_compile_options(${target} PUBLIC -Wno-source-uses-openmp)
65+
target_compile_options(${target} PUBLIC -Wno-switch-default)
66+
target_compile_options(${target} PUBLIC -Wno-disabled-macro-expansion)
6567

6668
endfunction()
6769

src/Mb_Proc_Signal.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
*/
55

66
#include "Mb_Proc_Signal.hpp"
7+
#include "Print_Time.hpp"
78

89
#include <cerrno>
10+
#include <format>
11+
#include <iostream>
912
#include <modbus/modbus.h>
1013
#include <system_error>
14+
#include <vector>
1115

1216
Mb_Proc_Signal Mb_Proc_Signal::instance; // NOLINT
1317

@@ -16,17 +20,34 @@ Mb_Proc_Signal &Mb_Proc_Signal::get_instance() {
1620
}
1721

1822
void Mb_Proc_Signal::add_process(pid_t process) {
23+
auto ret = kill(process, 0);
24+
if (ret == -1) {
25+
if (errno == ESRCH) { throw std::runtime_error(std::format("no such process: {}", process)); }
26+
throw std::system_error(
27+
errno, std::generic_category(), std::format("Failed to send signal to process {}", process));
28+
}
1929
processes.insert(process);
2030
}
2131

22-
void Mb_Proc_Signal::send_signal() {
32+
void Mb_Proc_Signal::send_signal(const union sigval &value) {
33+
std::vector<pid_t> erased;
2334
for (auto proc : processes) {
24-
auto ret = kill(proc, SIGUSR1);
35+
auto ret = sigqueue(proc, SIGUSR1, value);
2536
if (ret == -1) {
26-
throw std::system_error(
27-
errno, std::generic_category(), "Failed to send signal to process " + std::to_string(proc));
37+
if (errno == ESRCH) {
38+
erased.emplace_back(proc);
39+
} else {
40+
throw std::system_error(
41+
errno, std::generic_category(), std::format("Failed to send signal to process {}", proc));
42+
}
2843
}
2944
}
45+
46+
for (auto proc : erased) {
47+
std::cerr << Print_Time::iso << " WARNING: process " << proc
48+
<< " does no longer exist. Removing from SIGUSR1 receivers.\n";
49+
processes.erase(proc);
50+
}
3051
}
3152

3253
void mb_callback(uint8_t mb_funtion_code) {
@@ -35,7 +56,9 @@ void mb_callback(uint8_t mb_funtion_code) {
3556
case MODBUS_FC_WRITE_SINGLE_REGISTER:
3657
case MODBUS_FC_WRITE_MULTIPLE_COILS:
3758
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
38-
case MODBUS_FC_WRITE_AND_READ_REGISTERS: Mb_Proc_Signal::get_instance().send_signal(); break;
59+
case MODBUS_FC_WRITE_AND_READ_REGISTERS:
60+
Mb_Proc_Signal::get_instance().send_signal({.sival_int = mb_funtion_code});
61+
break;
3962
default:
4063
// do nothing
4164
break;

src/Mb_Proc_Signal.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Mb_Proc_Signal final {
2626

2727
void add_process(pid_t process);
2828

29-
void send_signal();
29+
void send_signal(const union sigval &value);
3030
};
3131

3232
void mb_callback(uint8_t mb_funtion_code);

src/Modbus_TCP_Client_poll.cpp

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "Modbus_TCP_Client_poll.hpp"
77

8+
#include "Mb_Proc_Signal.hpp"
89
#include "Print_Time.hpp"
910
#include "sa_to_str.hpp"
1011

@@ -13,6 +14,7 @@
1314
#include <netinet/tcp.h>
1415
#include <sstream>
1516
#include <stdexcept>
17+
#include <sys/signalfd.h>
1618
#include <sys/socket.h>
1719
#include <system_error>
1820

@@ -31,14 +33,15 @@ static constexpr long SEMAPHORE_ERROR_DEC = 1;
3133
static constexpr long SEMAPHORE_ERROR_MAX = 1000;
3234

3335
//* maximum time to wait for semaphore (100ms)
34-
static constexpr struct timespec SEMAPHORE_MAX_TIME = {0, 100'000};
36+
static constexpr struct timespec SEMAPHORE_MAX_TIME = {.tv_sec = 0, .tv_nsec = 100'000};
3537

3638
Client_Poll::Client_Poll(const std::string &host,
3739
const std::string &service,
40+
bool allow_sigusr1,
3841
modbus_mapping_t *mapping,
3942
std::size_t tcp_timeout, // NOLINT
4043
std::size_t max_clients) // NOLINT
41-
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) {
44+
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) {
4245
const char *host_str = "::";
4346
if (!(host.empty() || host == "any")) host_str = host.c_str();
4447

@@ -82,10 +85,11 @@ Client_Poll::Client_Poll(const std::string &host,
8285

8386
Client_Poll::Client_Poll(const std::string &host,
8487
const std::string &service,
88+
bool allow_sigusr1,
8589
std::array<modbus_mapping_t *, MAX_CLIENT_IDS> &mappings,
8690
std::size_t tcp_timeout, // NOLINT
8791
std::size_t max_clients) // NOLINT
88-
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) {
92+
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) {
8993
const char *host_str = "::";
9094
if (!(host.empty() || host == "any")) host_str = host.c_str();
9195

@@ -311,10 +315,33 @@ Client_Poll::run_t Client_Poll::run(int signal_fd,
311315
if (fd.revents & POLLNVAL) throw std::logic_error("poll (server socket) returned POLLNVAL");
312316
if (fd.revents & POLLERR) throw std::logic_error("poll (signal fd) returned POLLERR");
313317
if (fd.revents & POLLHUP) throw std::logic_error("poll (signal fd) returned POLLHUP");
314-
if (fd.revents & POLLIN) return run_t::term_signal;
315-
std::ostringstream sstr;
316-
sstr << "poll (signal fd) returned unknown revent: " << fd.revents;
317-
throw std::logic_error(sstr.str());
318+
if (fd.revents & POLLIN) {
319+
signalfd_siginfo siginfo {};
320+
const auto read_size = read(signal_fd, &siginfo, sizeof(siginfo));
321+
if (read_size == -1) {
322+
throw std::system_error(errno, std::generic_category(), "Failed to read signalfd");
323+
}
324+
325+
if (siginfo.ssi_signo == SIGUSR1 && allow_sigusr1) {
326+
const auto pid = siginfo.ssi_pid;
327+
try {
328+
Mb_Proc_Signal::get_instance().add_process(static_cast<pid_t>(pid));
329+
std::cerr << Print_Time::iso << " INFO: process " << pid
330+
<< " registered for SIGUSR1 on writing modbus commands\n";
331+
} catch (const std::runtime_error &err) {
332+
std::cerr << Print_Time::iso << " WARNING: process " << pid
333+
<< " registered for SIGUSR1: " << err.what() << "\n";
334+
}
335+
return run_t::ok;
336+
337+
} else {
338+
return run_t::term_signal;
339+
}
340+
} else {
341+
std::ostringstream sstr;
342+
sstr << "poll (signal fd) returned unknown revent: " << fd.revents;
343+
throw std::logic_error(sstr.str());
344+
}
318345
}
319346
}
320347

src/Modbus_TCP_Client_poll.hpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,39 @@ class Client_Poll {
4242
std::unique_ptr<cxxsemaphore::Semaphore> semaphore;
4343

4444
long semaphore_error_counter = 0;
45+
bool allow_sigusr1;
4546

4647
public:
4748
/*! \brief create modbus client (TCP server)
4849
*
4950
* @param host host to listen for incoming connections (default 0.0.0.0 (any))
5051
* @param service service/port to listen for incoming connections (default 502)
52+
* @param allow_sigusr1 allow other processes to register for SIGUSR1 on writing modbus commands by sending SIGUSR1
5153
* @param mapping modbus mapping object for all client ids
5254
* nullptr: an mapping object with maximum size is generated
5355
* @param tcp_timeout tcp timeout (currently only available on linux systems)
56+
* @param max_clients max number of allowed clients
5457
*/
55-
explicit Client_Poll(const std::string &host = "any",
56-
const std::string &service = "502",
57-
modbus_mapping_t *mapping = nullptr,
58-
std::size_t tcp_timeout = 5,
59-
std::size_t max_clients = 1);
58+
explicit Client_Poll(const std::string &host = "any",
59+
const std::string &service = "502",
60+
bool allow_sigusr1 = false,
61+
modbus_mapping_t *mapping = nullptr,
62+
std::size_t tcp_timeout = 5,
63+
std::size_t max_clients = 1);
6064

6165
/**
6266
* @brief create modbus client (TCP server) with dedicated mappings per client id
6367
*
6468
* @param host host to listen for incoming connections
6569
* @param service service/port to listen for incoming connections
70+
* @param allow_sigusr1 allow other processes to register for SIGUSR1 on writing modbus commands by sending SIGUSR1
6671
* @param mappings modbus mappings (one for each possible id)
6772
* @param tcp_timeout tcp timeout (currently only available on linux systems)
73+
* @param max_clients max number of allowed clients
6874
*/
6975
Client_Poll(const std::string &host,
7076
const std::string &service,
77+
bool allow_sigusr1,
7178
std::array<modbus_mapping_t *, MAX_CLIENT_IDS> &mappings,
7279
std::size_t tcp_timeout = 5,
7380
std::size_t max_clients = 1);

0 commit comments

Comments
 (0)