37 -- > procd for detailed explanation of basic software modules of OpenWRT system framework

Posted by rickead2000 on Thu, 30 Dec 2021 04:10:25 +0100

1, Introduction
There are many basic framework software of openwrt routing operating system, most of which are general software modules, such as dhcp, dnsmasq, iproute, cmwp, vpn, ipsec, etc; Openwrt also integrates some software modules with exclusive features, which are also the core framework software components of openwrt system. Starting from this article, we will analyze the series of articles on basic software modules of openwrt system framework.

OpenWrt core software: procd, uci, libubox, ubus, ubox, luci, netifd software component content. This part of the software module is the basic software constituting the OpenWrt framework.

There are many contents involved in the source code of procd. The author's previous articles "a detailed explanation of libubox of the basic software module of OpenWRT system framework" are all prepared to analyze the contents of procd and netifd. Please review the previous contents and read this article.

2, procd software architecture

Let's first look at the source code structure in the procd folder, cmakelist Txt file is the basis for project production. The key contents are as follows:

cmake_minimum_required(VERSION 2.6)
PROJECT(procd C)
INCLUDE(GNUInstallDirs)
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)

SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")

IF(APPLE)
  INCLUDE_DIRECTORIES(/opt/local/include)
  LINK_DIRECTORIES(/opt/local/lib)
ENDIF()

ADD_LIBRARY(setlbf SHARED service/setlbf.c)
INSTALL(TARGETS setlbf
	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

SET(SOURCES procd.c signal.c state.c inittab.c rcS.c ubus.c system.c sysupgrade.c
	service/service.c service/instance.c service/validate.c service/trigger.c service/watch.c
	utils/utils.c)
	
IF(NOT DISABLE_INIT)
  SET(SOURCES ${SOURCES} watchdog.c plug/coldplug.c plug/hotplug.c)  
ENDIF()

IF(ZRAM_TMPFS)
  ADD_DEFINITIONS(-DZRAM_TMPFS)
  SET(SOURCES_ZRAM initd/zram.c)
ENDIF()

add_subdirectory(upgraded)

// First system initialization / sbin/init thread
// The kernel retrieves the evn arg environment variable and executes the user state initialization thread / sbin/init; 
IF(DISABLE_INIT)
ADD_DEFINITIONS(-DDISABLE_INIT)
ELSE()
ADD_EXECUTABLE(init initd/init.c initd/early.c initd/preinit.c initd/mkdev.c sysupgrade.c watchdog.c
	utils/utils.c ${SOURCES_ZRAM})
TARGET_LINK_LIBRARIES(init ${LIBS})
INSTALL(TARGETS init
	RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

// Second procd thread
ADD_EXECUTABLE(procd ${SOURCES})
TARGET_LINK_LIBRARIES(procd ${LIBS})
INSTALL(TARGETS procd
	RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)


// Third udevtrigger device daemon thread (udev)
ADD_EXECUTABLE(udevtrigger plug/udevtrigger.c)
INSTALL(TARGETS udevtrigger
	RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
ENDIF()

//Fourth, utrace user space trace tool
IF(UTRACE_SUPPORT)
ADD_EXECUTABLE(utrace trace/trace.c)
TARGET_LINK_LIBRARIES(utrace ${ubox} ${json} ${blobmsg_json})
INSTALL(TARGETS utrace
	RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
ADD_DEPENDENCIES(utrace syscall-names-h)

There are many contents involved in this project, please refer to the notes in the document. This part is the realization of the core basic functions of OpenWrt system. Through this part, you can clearly see the system startup process. As the content covers a wide range, the process file of analyzing the source code is posted for your reference as the source code relationship map.

Source code analysis relationship record

1, The system starts the initialization / sbin/init thread

The kernel retrieves the evn arg environment variable and executes the user state initialization thread / sbin/init;

1.1 entry main function

int main(int argc, char **argv)

(1). Initialization semaphore
	ulog_open(ULOG_KMSG, LOG_DAEMON, "init");
	sigaction(SIGTERM, &sa_shutdown, NULL);
	sigaction(SIGUSR1, &sa_shutdown, NULL);
	sigaction(SIGUSR2, &sa_shutdown, NULL);
	sigaction(SIGPWR, &sa_shutdown, NULL);
	
(2).
	early();
(3).
	cmdline();
(4). 
	watchdog_init(1);
(5).
	pid = fork();
	if (!pid) {
		char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };

		if (debug < 3)
			patch_stdio("/dev/null");

		execvp(kmod[0], kmod);  // The fork() subprocess executes the execvp() function
		/*
		* Function Description: execvp() will find the file name matching the parameter file from the directory indicated by the PATH environment variable, execute the file after finding it, and then pass the second parameter argv to the file to be executed.
		* Return value: if the execution is successful, the function will not return. If the execution fails, it will directly return - 1. The failure reason is stored in errno
		* 
		* Execute the / sbin/kmodloader function
		* Execute / etc / modules boot Module initialization program under D / path,
		* root@ixeRouter:~# ls /etc/modules-boot.d/                                                                                                                    
		* 02-crypto-hash          30-fs-squashfs          50-usb-ohci
		* 04-crypto-crc32c        30-gpio-button-hotplug  50-usb-uhci
		* 09-crypto-aead          35-usb-ehci             51-usb-ohci-pci
		* 09-crypto-manager       40-scsi-core            54-usb3
		* 15-libphy               40-usb2                 60-leds-gpio
		* 15-mii                  41-ata-ahci             mmc
		* 20-usb-core             42-usb2-pci             sdhci-mt7620
		*/
		
		ERROR("Failed to start kmodloader: %m\n");
		exit(EXIT_FAILURE);
	}
	if (pid <= 0) {
		ERROR("Failed to start kmodloader instance: %m\n");
	} else {//The main thread executes the following program
		const struct timespec req = {0, 10 * 1000 * 1000};
		int i;

		for (i = 0; i < 1200; i++) {
			if (waitpid(pid, NULL, WNOHANG) > 0)
				break;
			nanosleep(&req, NULL);
			watchdog_ping();
		}
	}
(6).
	uloop_init();
	
(7).
	preinit();
		char *init[] = { "/bin/sh", "/etc/preinit", NULL };
		char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
		
		plugd_proc.pid = fork();									// fork() creates a child process
		if (!plugd_proc.pid) {
			execvp(plug[0], plug);									// The child process executes the / sbin/procd function, and the 1# thread is created successfully
			ERROR("Failed to start plugd: %m\n");
			exit(EXIT_FAILURE);
		}
		if (plugd_proc.pid <= 0) {
			ERROR("Failed to start new plugd instance: %m\n");
			return;
		}
		
		uloop_process_add(&plugd_proc);								// Put procd into uloop linked list

		setenv("PREINIT", "1", 1);

		fd = creat("/tmp/.preinit", 0600);

		if (fd < 0)
			ERROR("Failed to create sentinel file: %m\n");
		else
			close(fd);

		preinit_proc.cb = spawn_procd;
		preinit_proc.pid = fork();									//fork() creates a child process
		if (!preinit_proc.pid) {
			execvp(init[0], init);									// The child process executes / bin/sh, and the thread is created successfully
			ERROR("Failed to start preinit: %m\n");
			exit(EXIT_FAILURE);
		}
		if (preinit_proc.pid <= 0) {
			ERROR("Failed to start new preinit instance: %m\n");
			return;
		}
		uloop_process_add(&preinit_proc);							 hold spawn_procd put to uloop In the linked list
	
(8).
	uloop_run();

1.2 function spawn_procd entrance

static void spawn_procd(struct uloop_process *proc, int ret)
{
	char *wdt_fd = watchdog_fd();
	char *argv[] = { "/sbin/procd", NULL};
	
	if (plugd_proc.pid > 0)
		kill(plugd_proc.pid, SIGKILL);

	unsetenv("INITRAMFS");
	unsetenv("PREINIT");
	unlink("/tmp/.preinit");

	check_sysupgrade();						//Check for system software updates

	DEBUG(2, "Exec to real procd now\n");
	if (wdt_fd)
		setenv("WDTFD", wdt_fd, 1);
	check_dbglvl();
	if (debug > 0) {
		snprintf(dbg, 2, "%d", debug);
		setenv("DBGLVL", dbg, 1);
	}

	execvp(argv[0], argv);
}

1.3 system software check and update

static void check_sysupgrade(void)
{
	char *prefix = NULL, *path = NULL, *command = NULL;
	size_t n;

	if (chdir("/"))
		return;

	FILE *sysupgrade = fopen("/tmp/sysupgrade", "r");
	if (!sysupgrade)
		return;

	n = 0;
	if (getdelim(&prefix, &n, 0, sysupgrade) < 0)
		goto fail;
	n = 0;
	if (getdelim(&path, &n, 0, sysupgrade) < 0)
		goto fail;
	n = 0;
	if (getdelim(&command, &n, 0, sysupgrade) < 0)
		goto fail;

	fclose(sysupgrade);

	sysupgrade_exec_upgraded(prefix, path, NULL, command, NULL);  // Perform system upgrade / sbin/upgraded

	while (true)
		sleep(1);

fail:
	fclose(sysupgrade);
	free(prefix);
	free(path);
	free(command);
}

2, procd function analysis of process 1

2.0 procd. main function in C file

(1). 	
  uloop_init();
  (1.0)
	uloop_init_pollfd();
		epoll || kqueue initialization
  (1.1).	
	waker_init();
		waker_fd.cb = waker_consume()
		uloop_fd_add(&waker_fd, ULOOP_READ)
  (1.2).	
	uloop_setup_signals(true);
		uloop_install_handler(SIGINT, uloop_handle_sigint, &old_sigint, add);  	//Initialization signal SIGINT
		uloop_install_handler(SIGTERM, uloop_handle_sigint, &old_sigterm, add);	//Initialization signal SIGTERM
		uloop_install_handler(SIGCHLD, uloop_sigchld, &old_sigchld, add);		//Initialization signal SIGCHLD
		uloop_ignore_signal(SIGPIPE, add);										//Initialization signal SIGCHLD

(2).	
	procd_signal()
		sigaction(SIGTERM, &sa_shutdown, NULL);
		sigaction(SIGINT, &sa_shutdown, NULL);
		sigaction(SIGUSR1, &sa_shutdown, NULL);
		sigaction(SIGUSR2, &sa_shutdown, NULL);
		sigaction(SIGPWR, &sa_shutdown, NULL);
			signal_shutdown();						//reboot
		sigaction(SIGSEGV, &sa_crash, NULL);
		sigaction(SIGBUS, &sa_crash, NULL);
			do_reboot();							//reboot
		sigaction(SIGHUP, &sa_dummy, NULL);
		sigaction(SIGKILL, &sa_dummy, NULL);
		sigaction(SIGSTOP, &sa_dummy, NULL);
			signal_dummy();							//Output error message
		#ifndef DISABLE_INIT
		reboot(RB_DISABLE_CAD);						//
		#endif

(3).			
	if (getpid() != 1)
		procd_connect_ubus();						// Initialize the ubus bus
	else
		procd_state_next();							//Loop state machine variables to switch program running state
			void procd_state_next(void)
			{
				DEBUG(4, "Change state %d -> %d\n", state, state + 1);
				state++;			
				state_enter();						//Call the state machine processing function state_enter()
			}

(4).
	uloop_run();
	    uloop_run_timeout(-1);
			uloop_handle_processes();		// 
		    uloop_run_events(next_time);   // Execute RPC callback function of ubus
(5).
	uloop_done();

2.1 function state_enter()

//   State machine parameter definition
static int state = STATE_NONE;
enum {
	STATE_NONE = 0,
	STATE_EARLY,
	STATE_UBUS,
	STATE_INIT,
	STATE_RUNNING,
	STATE_SHUTDOWN,
	STATE_HALT,
	__STATE_MAX,
};

// Running order of state machine: state_ EARLY -> STATE_ UBUS -> STATE_ INIT -> STATE_ RUNNING;
// STATE_SHUTDOWN,STATE_HALT
static void state_enter(void)
{
	char ubus_cmd[] = "/sbin/ubusd";

	switch (state) {
	case STATE_EARLY:
		LOG("- early -\n");
		watchdog_init(0);						// Initialize watchdog
		hotplug("/etc/hotplug.json");			// Hot plug
		procd_coldplug();						// Cold plug
		break;

	case STATE_UBUS:
		// try to reopen incase the wdt was not available before coldplug
		watchdog_init(0);						// Initialize watchdog
		set_stdio("console");					// Set console parameters 
		LOG("- ubus -\n");
		procd_connect_ubus();					// Connecting the ubus bus
		service_start_early("ubus", ubus_cmd);	// Start RPC 
		break;

	case STATE_INIT:
		LOG("- init -\n");
		procd_inittab();						//Execute the script functions in the / etc/inittab file,
														::sysinit:/etc/init.d/rcS S boot
														::shutdown:/etc/init.d/rcS K shutdown
														::askconsole:/usr/libexec/login.sh
		procd_inittab_run("respawn");			//
		procd_inittab_run("askconsole");
		procd_inittab_run("askfirst");
		procd_inittab_run("sysinit");

		// switch to syslog log channel
		ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
		break;

	case STATE_RUNNING:
		LOG("- init complete -\n");
		procd_inittab_run("respawnlate");
		procd_inittab_run("askconsolelate");
		break;

	case STATE_SHUTDOWN:
		/* Redirect output to the console for the users' benefit */
		set_console();							// Redirect the console port according to the parameters passed from uboot to the / proc/cmdline file
		LOG("- shutdown -\n");
		procd_inittab_run("shutdown");			// Initialize shutdown callback function
		sync();
		break;

	case STATE_HALT:
		// To prevent killed processes from interrupting the sleep
		signal(SIGCHLD, SIG_IGN);
		LOG("- SIGTERM processes -\n");
		kill(-1, SIGTERM);
		sync();
		sleep(1);
		LOG("- SIGKILL processes -\n");
		kill(-1, SIGKILL);
		sync();
		sleep(1);
#ifndef DISABLE_INIT
		perform_halt();
#else
		exit(EXIT_SUCCESS);
#endif
		break;

	default:
		ERROR("Unhandled state %d\n", state);
		return;
	};
}

2.2 watchdog initialization, input parameter preinit=0

static struct uloop_timeout wdt_timeout;
static int wdt_fd = -1;
static int wdt_drv_timeout = 30;			//Watchdog timeout: 30s
static int wdt_frequency = 5;				//Dog feeding frequency: 5s
static bool wdt_magicclose = false;

void watchdog_init(int preinit)
{
	wdt_timeout.cb = watchdog_timeout_cb;

	if (watchdog_open(!preinit) < 0)
		return;

	LOG("- watchdog -\n");
	watchdog_set_drv_timeout();				//Set watchdog time = wdt_drv_timeout=30s
	watchdog_timeout_cb(&wdt_timeout);		

	DEBUG(4, "Opened watchdog with timeout %ds\n", watchdog_timeout(0));
}

2.3 inittab.c file procd_ Detailed explanation of inittab function

// Array handlers [] initialization parameters
static struct init_handler handlers[] = {
	{
		.name = "sysinit",
		.cb = runrc,				// Execute / etc / RC File under D path
	}, {
		.name = "shutdown",
		.cb = runrc,
	}, {
		.name = "askfirst",
		.cb = askfirst,
		.multi = 1,
	}, {
		.name = "askconsole",
		.cb = askconsole,
		.multi = 1,
	}, {
		.name = "respawn",
		.cb = rcrespawn,
		.multi = 1,
	}, {
		.name = "askconsolelate",
		.cb = askconsole,
		.multi = 1,
	}, {
		.name = "respawnlate",
		.cb = rcrespawn,
		.multi = 1,
	}
};


struct init_action {
	struct list_head list;

	char *id;
	char *argv[MAX_ARGS];
	char *line;

	struct init_handler *handler;
	struct uloop_process proc;

	int respawn;
	struct uloop_timeout tout;
};


static const char *tab = "/etc/inittab";
static char *ask = "/sbin/askfirst";
static LIST_HEAD(actions);

Entry function
void procd_inittab(void){
	
	// Read the contents of the / etc/inittab file and put:: sysinit: / etc / init d/rcS S boot
	//  										::shutdown:/etc/init.d/rcS K shutdown
	//											::askconsole:/usr/libexec/login.sh
	// Add the contents to the handlers [] array,
	
	while (fgets(line, LINE_LEN, fp)) {
		
		add_action(a, tags[TAG_ACTION])   //Add item by item to 
	}
}

// This fork is called in the callback function_ Worker, fork other threads.
static void fork_worker(struct init_action *a)
{
	pid_t p;

	a->proc.pid = fork();
	if (!a->proc.pid) {
		p = setsid();

		if (patch_stdio(a->id))
			ERROR("Failed to setup i/o redirection\n");

		ioctl(STDIN_FILENO, TIOCSCTTY, 1);
		tcsetpgrp(STDIN_FILENO, p);

		execvp(a->argv[0], a->argv);						//Give * a parameter, fork sub thread
		ERROR("Failed to execute %s: %m\n", a->argv[0]);
		exit(-1);
	}

	if (a->proc.pid > 0) {
		DEBUG(4, "Launched new %s action, pid=%d\n",
					a->handler->name,
					(int) a->proc.pid);
		uloop_process_add(&a->proc);						//Register * a on the ubus bus
	}
}

// Callback parameter of uloop thread
struct uloop_process
{
	struct list_head list;
	bool pending;

	uloop_process_handler cb;
	pid_t pid;
};

// Global linked list of rpc interface of ubus bus
static struct list_head timeouts = LIST_HEAD_INIT(timeouts);		//Timeout callback rpc interface function
static struct list_head processes = LIST_HEAD_INIT(processes);		//Explicit callback rpc interface function
// Register function
int uloop_process_add(struct uloop_process *p)
{
	struct uloop_process *tmp;
	struct list_head *h = &processes;

	if (p->pending)
		return -1;

	list_for_each_entry(tmp, &processes, list) {
		if (tmp->pid > p->pid) {
			h = &tmp->list;
			break;
		}
	}

	list_add_tail(&p->list, h);    //Add * p to the processes linked list and register it with the ubus bus,
	p->pending = true;

	return 0;
}

3, udevtrigger device scan thread (udev)

udevtrigger. Analysis of C file and main() entry function

This section is the device file system registration process

int main(int argc, char *argv[], char *envp[])
{
	openlog("udevtrigger", LOG_PID | LOG_CONS, LOG_DAEMON);
	
	scan_subdir("/sys/bus", "/devices", false, 1);
	scan_subdir("/sys/class", NULL, false, 1);					// Processing devices in / sys/class directory
	
	if (stat("/sys/class/block", &statbuf) != 0)
		scan_subdir("/sys/block", NULL, true, 1);				//Processing system partition information

exit:
	closelog();
	return 0;
	
}


// Recursive scan device directory

static void scan_subdir(const char *base, const char *subdir,
		        bool insert, int depth)
{
	DIR *dir;
	struct dirent *dent;

	dir = opendir(base);
	if (dir == NULL)
		return;

	for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
		char dirname[PATH_SIZE];

		if (dent->d_name[0] == '.')
			continue;

		strlcpy(dirname, base, sizeof(dirname));
		strlcat(dirname, "/", sizeof(dirname));
		strlcat(dirname, dent->d_name, sizeof(dirname));

		if (insert) {
			int err;

			err = device_list_insert(dirname);
			if (err)
				continue;
		}

		if (subdir)
			strlcat(dirname, subdir, sizeof(base));

		if (depth)
			scan_subdir(dirname, NULL, true, depth - 1);
	}

	closedir(dir);
}


// Add equipment to equipment linked list
static int device_list_insert(const char *path)
{
	char devpath[PATH_SIZE];
	struct stat statbuf;

	dbg("add '%s'" , path);

	/* we only have a device, if we have a dev and an uevent file */
	if (!device_has_attribute(path, "/dev", S_IRUSR) ||
	    !device_has_attribute(path, "/uevent", S_IWUSR))
		return -1;

	strlcpy(devpath, &path[4], sizeof(devpath));

	/* resolve possible link to real target */
	if (lstat(path, &statbuf) < 0)
		return -1;
	if (S_ISLNK(statbuf.st_mode))
		if (sysfs_resolve_link(devpath, sizeof(devpath)) != 0)
			return -1;

	trigger_uevent(devpath);
	return 0;
}

// Map the device file to memory and follow the specified path
static int sysfs_resolve_link(char *devpath, size_t size)
{
	char link_path[PATH_SIZE];
	char link_target[PATH_SIZE];
	int len;
	int i;
	int back;

	strlcpy(link_path, "/sys", sizeof(link_path));
	strlcat(link_path, devpath, sizeof(link_path));
	len = readlink(link_path, link_target, sizeof(link_target) - 1);
	/*
	* Definition function: int readlink(const char * path, char * buf, size_t bufsiz);
	* Function Description: readlink() will save the symbolic connection content of the parameter path to the memory space indicated by the parameter buf, 
	*           The returned content does not end in NULL, but returns the number of characters of the string
	*			If the parameter bufsiz is less than the content length of the symbolic connection, the too long content will be truncated
	* Return value: if the execution is successful, the file path string indicated by the symbolic connection is passed. If it fails, it returns - 1. The error code is stored in errno
	*/
	if (len <= 0)
		return -1;
	link_target[len] = '\0';
	dbg("path link '%s' points to '%s'", devpath, link_target);

	for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
		;
	dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back);
	for (i = 0; i <= back; i++) {
		char *pos = strrchr(devpath, '/');

		if (pos == NULL)
			return -1;
		pos[0] = '\0';
	}
	dbg("after moving back '%s'", devpath);
	strlcat(devpath, "/", size);
	strlcat(devpath, &link_target[back * 3], size);
	return 0;
}