From 2bf2115ab5bdc4efd906cada0740171aff456c2b Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Tue, 5 Dec 2023 18:07:24 +0100 Subject: [PATCH] Add a Unix socket connection Unix-domain sockets have less overhead than TCP. --- DOC/src/html/sif.html | 6 +++ command.c | 1 + pigpio.3 | 30 +++++++++++ pigpio.c | 116 ++++++++++++++++++++++++++++++++++++++++++ pigpio.h | 21 ++++++++ pigpio.py | 35 ++++++++----- pigpiod.c | 12 ++++- 7 files changed, 208 insertions(+), 13 deletions(-) diff --git a/DOC/src/html/sif.html b/DOC/src/html/sif.html index 67b880b..b12eb97 100644 --- a/DOC/src/html/sif.html +++ b/DOC/src/html/sif.html @@ -25,6 +25,12 @@ default may be overridden when pigpio starts by the
+pigpio also listens for connections on "/var/run/pigpio.sock" by +default.  This default may be overridden when pigpio starts +by the
gpioCfgSocketPath +function call.  The pigpio daemon uses this function to provide an +option to change the socket file name.
+
The pigs utility is an example of using the socket interface from C.

Request

diff --git a/command.c b/command.c index ffc3463..d17b522 100644 --- a/command.c +++ b/command.c @@ -567,6 +567,7 @@ static errInfo_t errInfo[]= {PI_CMD_INTERRUPTED , "command interrupted, Python"}, {PI_NOT_ON_BCM2711 , "not available on BCM2711"}, {PI_ONLY_ON_BCM2711 , "only available on BCM2711"}, + {PI_BAD_SOCKET_PATH , "socket path empty"}, }; diff --git a/pigpio.3 b/pigpio.3 index 783bc92..a5b7075 100644 --- a/pigpio.3 +++ b/pigpio.3 @@ -706,6 +706,8 @@ gpioCfgInterfaces Configure user interfaces .br gpioCfgSocketPort Configure socket port .br +gpioCfgSocketPath Configure socket path +.br gpioCfgMemAlloc Configure DMA memory allocation mode .br gpioCfgNetAddr Configure allowed network addresses @@ -7802,6 +7804,30 @@ port: 1024-32000 .br The default setting is to use port 8888. +.IP "\fBint gpioCfgSocketPath(const char*)\fP" +.IP "" 4 +Configures pigpio to use the specified Unix socket. + +.br + +.br +This function is only effective if called before \fBgpioInitialise\fP. + +.br + +.br + +.EX +path: path to the socket. +.br + +.EE + +.br + +.br +The default is "/var/run/pigpio.sock". + .IP "\fBint gpioCfgInterfaces(unsigned ifFlags)\fP" .IP "" 4 Configures pigpio support of the fifo and socket interfaces. @@ -8945,6 +8971,8 @@ These functions are only effective if called before \fBgpioInitialise\fP. .br \fBgpioCfgSocketPort\fP .br +\fBgpioCfgSocketPath\fP +.br \fBgpioCfgMemAlloc\fP .br @@ -11040,6 +11068,8 @@ A 16-bit word value. .br #define PI_ONLY_ON_BCM2711 -146 // only available on BCM2711 .br +#define PI_BAD_SOCKET_PORT -147 // socket path empty +.br .br #define PI_PIGIF_ERR_0 -2000 diff --git a/pigpio.c b/pigpio.c index 97bfc54..5b83292 100644 --- a/pigpio.c +++ b/pigpio.c @@ -58,6 +58,7 @@ For more information, please refer to #include #include #include +#include #include #include #include @@ -1096,6 +1097,7 @@ typedef struct unsigned DMAprimaryChannel; unsigned DMAsecondaryChannel; unsigned socketPort; + const char * socketPath; unsigned ifFlags; unsigned memAllocMode; unsigned dbgLevel; @@ -1292,6 +1294,7 @@ static volatile int runState = PI_STARTING; static int pthAlertRunning = PI_THREAD_NONE; static int pthFifoRunning = PI_THREAD_NONE; static int pthSocketRunning = PI_THREAD_NONE; +static int pthUnixRunning = PI_THREAD_NONE; static gpioAlert_t gpioAlert [PI_MAX_USER_GPIO+1]; @@ -1328,6 +1331,7 @@ static FILE * outFifo = NULL; static int fdLock = -1; static int fdMem = -1; static int fdSock = -1; +static int fdUnix = -1; static int fdPmap = -1; static int fdMbox = -1; @@ -1369,6 +1373,7 @@ static volatile gpioCfg_t gpioCfg = PI_DEFAULT_DMA_NOT_SET, /* primary DMA */ PI_DEFAULT_DMA_NOT_SET, /* secondary DMA */ PI_DEFAULT_SOCKET_PORT, + PI_DEFAULT_SOCKET_PATH, PI_DEFAULT_IF_FLAGS, PI_DEFAULT_MEM_ALLOC_MODE, 0, /* dbgLevel */ @@ -1384,6 +1389,7 @@ static unsigned bufferCycles; /* number of cycles */ static pthread_t pthAlert; static pthread_t pthFifo; static pthread_t pthSocket; +static pthread_t pthUnix; static uint32_t spi_dummy; @@ -7260,6 +7266,61 @@ static void * pthSocketThread(void *x) return 0; } +static void * pthUnixThread(void *x) +{ + int fdC=0, c, *sock; + struct sockaddr_storage client; + pthread_attr_t attr; + + if (pthread_attr_init(&attr)) + SOFT_ERROR((void*)PI_INIT_FAILED, + "pthread_attr_init failed (%m)"); + + if (pthread_attr_setstacksize(&attr, STACK_SIZE)) + SOFT_ERROR((void*)PI_INIT_FAILED, + "pthread_attr_setstacksize failed (%m)"); + + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) + SOFT_ERROR((void*)PI_INIT_FAILED, + "pthread_attr_setdetachstate failed (%m)"); + + /* fdUnix opened in gpioInitialise so that we can treat + failure to bind as fatal. */ + + listen(fdUnix, 100); + + c = sizeof(client); + + /* don't start until DMA started */ + + spinWhileStarting(); + + while (fdC >= 0) + { + pthread_t thr; + + fdC = accept(fdUnix, (struct sockaddr *)&client, (socklen_t*)&c); + + closeOrphanedNotifications(-1, fdC); + + DBG(DBG_USER, "Connection accepted on socket %d", fdC); + + sock = malloc(sizeof(int)); + + *sock = fdC; + + if (pthread_create + (&thr, &attr, pthSocketThreadHandler, (void*) sock) < 0) + SOFT_ERROR((void*)PI_INIT_FAILED, + "socket pthread_create failed (%m)"); + } + + if (fdC < 0) + SOFT_ERROR((void*)PI_INIT_FAILED, "accept failed (%m)"); + + return 0; +} + /* ======================================================================= */ static void initCheckLockFile(void) @@ -7969,6 +8030,7 @@ static void initClearGlobals(void) pthAlertRunning = PI_THREAD_NONE; pthFifoRunning = PI_THREAD_NONE; pthSocketRunning = PI_THREAD_NONE; + pthUnixRunning = PI_THREAD_NONE; wfc[0] = 0; wfc[1] = 0; @@ -8051,6 +8113,7 @@ static void initClearGlobals(void) fdLock = -1; fdMem = -1; fdSock = -1; + fdUnix = -1; dmaMboxBlk = MAP_FAILED; dmaPMapBlk = MAP_FAILED; @@ -8120,6 +8183,13 @@ static void initReleaseResources(void) pthSocketRunning = PI_THREAD_NONE; } + if (pthUnixRunning != PI_THREAD_NONE) + { + pthread_cancel(pthUnix); + pthread_join(pthUnix, NULL); + pthUnixRunning = PI_THREAD_NONE; + } + /* release mmap'd memory */ if (auxReg != MAP_FAILED) munmap((void *)auxReg, AUX_LEN); @@ -8224,6 +8294,12 @@ static void initReleaseResources(void) fdSock = -1; } + if (fdUnix != -1) + { + close(fdUnix); + fdUnix = -1; + } + if (fdPmap != -1) { close(fdPmap); @@ -8248,7 +8324,9 @@ int initInitialise(void) unsigned rev, model; struct sockaddr_in server; struct sockaddr_in6 server6; + struct sockaddr_un serverU; char * portStr; + const char * sockStr; unsigned port; struct sched_param param; pthread_attr_t pthAttr; @@ -8368,6 +8446,29 @@ int initInitialise(void) pthFifoRunning = PI_THREAD_STARTED; } + if (!(gpioCfg.ifFlags & PI_DISABLE_UNIX_IF)) + { + sockStr = getenv(PI_ENVSOCK); + if (! sockStr) sockStr = gpioCfg.socketPath; + + fdUnix = socket(AF_UNIX, SOCK_STREAM , 0); + + if (fdUnix != -1) + { + bzero((char *)&serverU, sizeof(serverU)); + serverU.sun_family = AF_UNIX; + strncpy (serverU.sun_path, sockStr, sizeof (serverU.sun_path) - 1); + + if (bind(fdUnix,(struct sockaddr *)&serverU, sizeof(serverU)) < 0) + SOFT_ERROR(PI_INIT_FAILED, "bind to socket '%s' failed (%m)", sockStr); + } + + if (pthread_create(&pthUnix, &pthAttr, pthUnixThread, &i)) + SOFT_ERROR(PI_INIT_FAILED, "pthread_create unix failed (%m)"); + + pthUnixRunning = PI_THREAD_STARTED; + } + if (!(gpioCfg.ifFlags & PI_DISABLE_SOCK_IF)) { portStr = getenv(PI_ENVPORT); @@ -13972,6 +14073,21 @@ int gpioCfgSocketPort(unsigned port) } +int gpioCfgSocketPath(const char *path) +{ + DBG(DBG_USER, "path=%s", path); + + CHECK_NOT_INITED; + + if (!path || !*path) + SOFT_ERROR(PI_BAD_SOCKET_PATH, "bad path"); + + gpioCfg.socketPath = path; + + return 0; +} + + /* ----------------------------------------------------------------------- */ int gpioCfgMemAlloc(unsigned memAllocMode) diff --git a/pigpio.h b/pigpio.h index 1b8e51c..35e98c4 100644 --- a/pigpio.h +++ b/pigpio.h @@ -385,6 +385,7 @@ gpioCfgDMAchannels Configure the DMA channels gpioCfgPermissions Configure the GPIO access permissions gpioCfgInterfaces Configure user interfaces gpioCfgSocketPort Configure socket port +gpioCfgSocketPath Configure Unix socket path gpioCfgMemAlloc Configure DMA memory allocation mode gpioCfgNetAddr Configure allowed network addresses @@ -415,6 +416,7 @@ OVERVIEW*/ #define PI_ENVPORT "PIGPIO_PORT" #define PI_ENVADDR "PIGPIO_ADDR" +#define PI_ENVSOCK "PIGPIO_SOCKET" #define PI_LOCKFILE "/var/run/pigpio.pid" @@ -890,6 +892,7 @@ typedef void *(gpioThreadFunc_t) (void *); #define PI_DISABLE_SOCK_IF 2 #define PI_LOCALHOST_SOCK_IF 4 #define PI_DISABLE_ALERT 8 +#define PI_DISABLE_UNIX_IF 16 /* memAllocMode */ @@ -4919,6 +4922,21 @@ The default setting is to use port 8888. D*/ +/*F*/ +int gpioCfgSocketPath(const char * path); +/*D +Configures pigpio to use the specified Unix socket. + +This function is only effective if called before [*gpioInitialise*]. + +. . +port: path to the socket file. +. . + +The default is "/var/run/pigpio.sock". +D*/ + + /*F*/ int gpioCfgInterfaces(unsigned ifFlags); /*D @@ -5538,6 +5556,7 @@ These functions are only effective if called before [*gpioInitialise*]. [*gpioCfgPermissions*] [*gpioCfgInterfaces*] [*gpioCfgSocketPort*] +[*gpioCfgSocketPath*] [*gpioCfgMemAlloc*] gpioGetSamplesFunc_t:: @@ -6528,6 +6547,7 @@ after this command is issued. #define PI_CMD_INTERRUPTED -144 // Used by Python #define PI_NOT_ON_BCM2711 -145 // not available on BCM2711 #define PI_ONLY_ON_BCM2711 -146 // only available on BCM2711 +#define PI_BAD_SOCKET_PATH -147 // socket path empty #define PI_PIGIF_ERR_0 -2000 #define PI_PIGIF_ERR_99 -2099 @@ -6550,6 +6570,7 @@ after this command is issued. #define PI_DEFAULT_DMA_PRIMARY_CH_2711 7 #define PI_DEFAULT_DMA_SECONDARY_CH_2711 6 #define PI_DEFAULT_DMA_NOT_SET 15 +#define PI_DEFAULT_SOCKET_PATH "/var/run/pigpio.sock" #define PI_DEFAULT_SOCKET_PORT 8888 #define PI_DEFAULT_SOCKET_PORT_STR "8888" #define PI_DEFAULT_SOCKET_ADDR_STR "localhost" diff --git a/pigpio.py b/pigpio.py index 20d48e6..8d6e027 100644 --- a/pigpio.py +++ b/pigpio.py @@ -723,6 +723,7 @@ PI_BAD_EVENT_ID =-143 PI_CMD_INTERRUPTED =-144 PI_NOT_ON_BCM2711 =-145 PI_ONLY_ON_BCM2711 =-146 +_PI_BAD_SOCKET_PATH =-147 # pigpio error text @@ -871,6 +872,7 @@ _errors=[ [PI_CMD_INTERRUPTED , "pigpio command interrupted"], [PI_NOT_ON_BCM2711 , "not available on BCM2711"], [PI_ONLY_ON_BCM2711 , "only available on BCM2711"], + [_PI_BAD_SOCKET_PATH , "socket path empty"], ] _except_a = "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n{}" @@ -5177,6 +5179,7 @@ class pi(): def __init__(self, host = os.getenv("PIGPIO_ADDR", 'localhost'), port = os.getenv("PIGPIO_PORT", 8888), + sock = os.getenv("PIGPIO_SOCKET", None), show_errors = True): """ Grants access to a Pi's GPIO. @@ -5201,6 +5204,7 @@ class pi(): pi = pigio.pi() # use defaults pi = pigpio.pi('mypi') # specify host, default port pi = pigpio.pi('mypi', 7777) # specify host and port + pi = pigpio.pi(sock='/run/pigpio.sock') # specify a Unix socket pi = pigpio.pi() # exit script if no connection if not pi.connected: @@ -5212,19 +5216,24 @@ class pi(): self.sl = _socklock() self._notify = None - port = int(port) - - if host == '': - host = "localhost" - - self._host = host - self._port = port - + self._sock = sock try: - self.sl.s = socket.create_connection((host, port), None) + if sock: + self.sl.s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM) + self.sl.s.connect(sock) + else: + port = int(port) - # Disable the Nagle algorithm. - self.sl.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if host == '': + host = "localhost" + + self._host = host + self._port = port + + self.sl.s = socket.create_connection((host, port), None) + + # Disable the Nagle algorithm. + self.sl.s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self._notify = _callback_thread(self.sl, host, port) @@ -5264,7 +5273,9 @@ class pi(): print(_except_z) def __repr__(self): - return "".format(self._host, self._port) + if self._sock is None: + return "".format(self._host, self._port) + return "".format(self._sock) def stop(self): """Release pigpio resources. diff --git a/pigpiod.c b/pigpiod.c index 899ddf8..46d094f 100644 --- a/pigpiod.c +++ b/pigpiod.c @@ -59,6 +59,7 @@ static int foreground = PI_DEFAULT_FOREGROUND; static unsigned DMAprimaryChannel = PI_DEFAULT_DMA_NOT_SET; static unsigned DMAsecondaryChannel = PI_DEFAULT_DMA_NOT_SET; static unsigned socketPort = PI_DEFAULT_SOCKET_PORT; +static char * socketPath = PI_DEFAULT_SOCKET_PATH; static unsigned memAllocMode = PI_DEFAULT_MEM_ALLOC_MODE; static uint64_t updateMask = -1; @@ -106,6 +107,7 @@ void usage() " -n IP addr, allow address, name or dotted, default allow all\n" \ " -p value, socket port, 1024-32000, default 8888\n" \ " -s value, sample rate, 1, 2, 4, 5, 8, or 10, default 5\n" \ + " -S path, Unix socket file, default /var/run/pigpio.sock\n" \ " -t value, clock peripheral, 0=PWM 1=PCM, default PCM\n" \ " -v, -V, display pigpio version and exit\n" \ " -x mask, GPIO which may be updated, default board GPIO\n" \ @@ -163,7 +165,7 @@ static void initOpts(int argc, char *argv[]) uint32_t addr; int64_t mask; - while ((opt = getopt(argc, argv, "a:b:c:d:e:fgkln:mp:s:t:x:vV")) != -1) + while ((opt = getopt(argc, argv, "a:b:c:d:e:fgkln:mp:s:S:t:x:vV")) != -1) { switch (opt) { @@ -256,6 +258,12 @@ static void initOpts(int argc, char *argv[]) } break; + case 'S': + if (!*optarg) + fatal("invalid -S option (empty string)"); + socketPath = optarg; + break; + case 't': i = getNum(optarg, &err); if ((i >= PI_CLOCK_PWM) && (i <= PI_CLOCK_PCM)) @@ -358,6 +366,8 @@ int main(int argc, char **argv) gpioCfgSocketPort(socketPort); + gpioCfgSocketPath(socketPath); + gpioCfgMemAlloc(memAllocMode); if (updateMaskSet) gpioCfgPermissions(updateMask);