Let's contextualize some main terms and concepts first.
1.2 What is a Service?A service, in a really short and simplified way, means a binary program that runs in the background on an operating system (OS) doing something that is necessary either for the system to work properly or to provide some resource for some software application. On Windows and Linux-based OS, we have a lot of services running in the background, and many of them run when the hardware turns on.
1.2 SysVinit vs SystemdSystemd and SysVinit (system five init) are both initialization systems that take care of services running on a Linux-based OS (there are others, but for now, let's focus on these two).
Systemd is the most modern initialization system, used in many desktop distributions. If you use a Linux-based desktop distro, you probably already use some commands ending with "ctl" (like "systemctl
" or "journalctl
"), but on OpenLinux running on NL668 modules, it's not booted using systemd, so these types of commands ending with "ctl" will not work on it.
SysVinit is an initialization system that came before systemd and was already used in famous Linux distributions like RedHat. That is the method used in NL668 OpenLinux.
1.3 SysVinit and RunlevelsSysVinit is an init system based on runlevels, which means that the initialization of the OS is segmented into levels numbered from 0 to 6, where each runlevel is for tasks with one specific nature.
In the specific case of OpenLinux on NL668, the default runlevel is 5 (despite the fact that it doesn't have a graphic user interface). So, when the module starts up, it passes from runlevel 1 to runlevel 5. If you tell it to reboot, it goes from runlevel 5 to runlevel 6 (not necessarily in this order, but that's roughly how it works).
You may be wondering, "But what are these runlevels? How do I find them?" Let's take a look at the directories where they are located.
First, let's confirm which runlevel we are running using the runlevel
command in our adb shell:
In Linux systems, a lot of information about the general configuration of how the system works is located in the /etc
directory, so let's analyze it:
We are interested in these folders and files, which I have marked in red. Let's discuss them:
init.d
- This is a folder that contains all scripts that run some service.inittab
- This is a file that contains information about what runlevel it is, and where the init process (PID 1) that will run everything is located.rc0.d
torc6.d
- These are folders that contain symbolic links to initialization scripts insideinit.d
.rcS.d
- This is a subcategory of the runlevel that comes before runlevel 1; it's like a startup single user runlevel.
- NL668-LA Module (or any other Fibocom Module)
- GCC (sudo apt-get install -y gcc-arm-linux-gnueabi)
- Linux Operating System (can be a virtual machine or WSL)
- ADB (Android Debug Bridge)
There are some steps to follow in order to create a service. First, we need to have in hand what we want to run as a service, whether it's a binary application or maybe a script. Then, we write a script for this service and, after deciding on which runlevel we want to initiate this service, we upload it to init.d
and create a symlink from the runlevel folder that we want to run, giving it a priority within that runlevel.
One service that might be interesting to me is to run a program that fixes the hour time. If you are using the NL668 Fibocom development kit, you might have noticed that every time the module reboots, the system time is wrong and goes back to January 1st, 1970, right? This happens because the development kit doesn't have an RTC chip.
Here is a C program that runs a system command that tells the OS to access an NTP server and adjust the time to UTC:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define ERROR_CODE -1
int main(){
int result=ERROR_CODE;
do{
sleep(5);
result = system("ntpd -d -p a.st1.ntp.br");
}while(result==ERROR_CODE);
exit(EXIT_SUCCESS);
}
I compile this using the arm-linux-gnueabi cross-toolchain into an executable named:
exe_time_adjust
Then, I load it using adb to the folder /usr/bin
. You can load it to any folder you want, the most recomended is /data
and then create a symlink to /usr/bin.
## Running from your computer terminal:
adb shell mount -o rw,remount /
adb shell mkdir -p /data/bin
adb push exe_time_adjust /data/bin
adb shell ln -s /data/bin/exe_time_adjust /usr/bin/exe_time_adjust
adb shell chmod 755 /data/bin/exe_time_adjust
3.2 - Writing the scriptIn order to have a service on SysVinit is necessary to write a script with the initialization settings. The best way to do that is using a default script:
#!/bin/sh
### BEGIN INIT INFO
# Provides: timeadjust
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Adjust system time using NTP
# Description: Adjust system time using NTP server from a binary elf file
### END INIT INFO
## Service name
NAME=exe_time_adjust
## Path to log file of service
LOGFILE=/var/log/$NAME.log
## Path to PID file of service
PIDFILE=/var/run/$NAME.pid
## Path to service binary
BINARY=/usr/bin/$NAME
## Load configs of service
if [ -f /etc/$NAME.conf ]; then
. /etc/$NAME.conf
fi
## Start service
start() {
if [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting service...' >&2
local CMD="$BINARY &> \"$LOGFILE\" & echo \$!"
su -c "$CMD" $RUNAS > "$PIDFILE"
echo 'Service started' >&2
}
## Stop the service
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stoping service...' >&2
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
echo 'Service stoped' >&2
}
## Check service status
status() {
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE"); then
echo 'Service is running' >&2
else
echo 'Service is not running' >&2
fi
}
## Shows script help
help() {
echo "Usage: $0 {start|stop|status|restart}"
}
## Read command line arg
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
stop
start
;;
*)
help
exit 1
;;
esac
In the script above we can see some essencial elements, let's talk about them:
#!/bin/sh
: It's the script header, it told how to interpret this script### BEGIN INIT INFO
... : Description section about the service.NAME
,LOGFILE
,PIDFILE
andBINARY
: These are environment variables used across the script. Here is where we specify the binary that represent our service or maybe another script or command. As this process is run on background is also created some files with information to track the process, like the pid(process id) and the log file.if block
: This block checks if there any.conf
file with information about how to configure this service. If any just comment this block.start()
: This is a shell script function that runs everthing needed to start the service and also some messages.stop()
: Same as before but this time to stop the service.status()
: Shell function to inform if the service is running.help()
: Shell function to tell about script usage.case "$1" in...
: A `case`
statement to read the command line argument passed to the script.
You may realize that I already changed the name of the service to the binary executable name. It's a good practice give the service NAME
environment variable the same name of the binary and keep in /usr/bin
a symbolic link to the executable, in that way this service script willl work for any service and you just have to change the NAME
env variable.
Now it's time to load the script to /etc/init.d
folder, I named the script as:
start_exe_time_adjust
adb shell mount -o rw,remount /
adb push start_exe_time_adjust /etc/init.d
adb shell chmod 755 /etc/init.d/start_exe_time_adjust
3.3 - Creating symbolic link and activating the serviceDetermine the runlevel in which you want to enable the service. As mentioned before each runlevel corresponds to a specific set of services that are started when the system is booted. For example, runlevel 3 is typically used to start multiuser mode with networking, while runlevel 5 is used to start graphical mode. If you have some doubt just use the runlevel 5 (It's safer and guarantees that your service be started last).
adb shell
cd /etc/rc5.d
ln -s ../init.d/start_exe_time_adjust /etc/rc5.d/S99start_exe_time_adjust
The symbolic link will have the prefix "S" or "K", followed by a number and the service name. The prefix "S" means "start" and indicates that the service should be started in the specified runlevel. The prefix "K" means "kill" and indicates that the service should be stopped in the specified runlevel. The number is used to control the order in which services are started or stopped. Services with lower numbers are started or stopped first.
4 - Testing the serviceNow it's time to test if our service works! Let's restart the module and check if it will fix the time.
Looks like everthing is right! Congratulations, you made your first service on NL668 module.
If you make a process that don't exits after doing something and keep running on background you will be able to see this process running the ps
command. Like:
Or if you want to a real time tool to check the processes running use the top
command:
One last thing to consider is that in production scenarios you can do everthing that you did here but instead of access the NL668 OpenLinux module terminal you just does it on fibo_rootfs
folder.
In this tutorial, we learned how to create a service in a Linux-based system using SysVinit and the `init.d` and `rcX.d` folders. We also learned how to write a script and create a symbolic link to run it as a service in a specific runlevel.
I hope this tutorial was helpful for you. Let me know if you have any questions or comments. That's all, happy hacking!
Comments
Please log in or sign up to comment.