This article is for those who are still very fond of the ancient Unix, but already understands that urgently need to migrate to Linux ...
This story began with the fact that our esteemed customer decided to reduce the risks of operating equipment of 10 years of age, save on licenses and switch from Unix to Linux, and at the same time virtualize this hardware and software solution. It’s not that the customer disliked Solaris and Unix, it’s just the ability to virtualize a server application that is tightly tied to the specific SPARC architecture and the “graying” Solaris operating system looked very attractive to the customer. A separate point was the question of replacing a specialized card with a PCI interface with an affordable “virtualized” solution. We decided to take on such an interesting task.
Before we saw the source code, we had little idea what we were dealing with. Later, having familiarized ourselves with the task, we saw that the UNIX daemon is:
- "Tied" to the system calls of the Solaris kernel, but flexibly customizable, multi-threaded Web server ( daemon ) with an external Web API
- the code tied to the C-shnomu API specialized card in the PCI slot
- Unix - IPC Calls to PGP
- code tied to a dozen specialized Unix libraries
And this code and software and hardware solution needed to be “virtualized” and made to work in Linux.
')
Digression from the topicNow I understand that porting this project was extremely risky ...
The task "in the forehead" is not solved, so we decided to break the task into parts.
Separately, I wanted to say that the demon code turned out to be endian - neutral, which somewhat simplified the task.
In the process of finding the best way to port this code, we investigated
the IBM technical guide on porting Solaris, an application on Linux .
The first thing I wanted to find out was how difficult it would be to
port the Solaris kernel calls to relatively POSIX compatible Linux? Solaris, being a POSIX certified OS, has at its core very specific threading functions. Naturally, there are those that have no analogues in the Linux kernel:
- thr_suspend ();
- thr_continue ();
The daemon's functionality not only fully relied on these, but also on other Solaris kernel functions: thr_keycreate (), thr_self (), thr_getspecific (), and others. The IBM tutorial explicitly stated that such code would have to be rewritten when porting to Linux. And this meant that thousands of lines of multi-threaded low-level “C with classes” code needed to be analyzed and rewritten. Is there no other way? And it seemed to us that we found it! While searching for a working “crutch” for porting code for Solaris “as is” in Linux, we came across an old Compaq
Solaris-compatible Thread Library (ScTL) project . Hurray, we have a "layer" that will fall between the ported daemon and the Linux kernel! But it was not there…
At first, this project did not want to be assembled, since the Compaq company, which had initially died in a wagon train, conceived a project for Tru64 UNIX, then ported the project to Linux, Intel x86 and other platforms. Through trial and error after fixing the ScTL source, we were able to build the required libraries for the new Linux kernel on AMD64. The Linux daemon saw the “native” for it and Solaris-specific functions, and even pretended that it could work, but that was all. In the process of working with ScTL, there were exceptions of execution time in the ScTL code itself and the daemon “crashed”. There was no time to understand this 13-year “freshness” code, we began to look for an alternative. The choice was obvious, I wanted maximum compatibility with C ++ 11 and STL, so we decided to stop at std :: thread and not to communicate with the POSIX standard for threads directly. Since the daemon was a web server, it was highly desirable for us to switch from low-level sockets to Boost :: Asio. Comparing solutions, we decided to use a relatively simple, but already usable in third-party projects (MIT license)
project on GitHub . In application classes, std :: async and, of course, lambdas came in handy to ensure asynchrony from C ++ 11. So, we replaced the web server and it passed separate tests.
The next problem is to decide
how to “virtualize” a specialized expansion card with a PCI interface ? Similar functionality was available on the market, but it is also a
hardware solution, albeit with a USB interface. Since the hypervisor used by the customer allowed us to connect USB equipment to a virtual system, we came to a solution in which a hardware solution with a USB interface was connected to a virtual environment and made available to our application in Linux. All this required significant processing of the PCI-related code card, writing a new code for working with USB equipment and subsequent testing.
The third problem was
PGP . Its free analog GnuPG did not want to work in Linux according to the required scenario as in Solaris and we had to rewrite IPC to the gpgme library calls, as a result reliability increased, the daemon worked fine with its native C-shnye calls and began to correctly handle errors in abnormal situations.
There remained the fourth problem - dependence on Unix libraries. But after solving the previous problems, these libraries did not seem such a big problem. We built a Linux packet mapping table (analogs) to Solaris packages, then we figured out which packages contain the libraries we need and for what purposes and how our daemon wants to use them. As a result, all the required free analogues were found, the build and tests showed that the Unix-like Linux OS makes it easier to port the code base.
In conclusion, I would like to talk about two points: the build system we use and the target Linux distributions. In the original with the code for Solaris we got the Makefile. It was a good decision 20 years ago, but we wanted flexibility in building and
CMake gave us what we were looking for: customize work with Boost, link with different versions of libraries, take into account
changes in ABI in g ++ . Now
CMake allows us to conveniently generate files for Debug and Release assemblies. With target distributions, the situation was such that the Customer wanted to use Red Hat Enterprise Linux, and we used Ubuntu in our enterprise. The case ended with a static compilation of the Release assembly with minimal dependencies (hello to fans of .Net and Java), although the size of the executable file was somewhat “grown up”.
As a result, we ported the Solaris daemon to Linux, but at the same time we threw out almost the entire system code rooted in Solaris UNIX, wrote a new code to “virtualize” a PCI card into a similar USB device and made certain places in the code more reliable . Importantly, our new Linux daemon reads configuration files copied from Solaris and correctly configures its work on them, while maintaining 100% compatibility with the API specification as a Web service.
Thanks to the C ++ 11 standard (and
C ++ 17 on the way ), Boost, CMake, and modern hypervisors, the technical assignment, which at first seemed impracticable, turned out to be quite feasible and even fascinating.