By the fervor of Linus Torvalds, there does not exist any immediate C++ or OOP interfaces to operating system services. Consequently, it is sometimes necessary to wrap a logical set of Linux system calls in a C++ wrapper.
In this post, I will demonstrate a standard process of wrapping the resource limitation and usage system calls in a ResourceManager singleton service while utilizing some nifty C++ tricks such as CRTP.
Motivation
A procedural style offers the same behavior as an OOP-style would; however, OOP makes optimal use of abstractions by providing common design patterns. Consequently, refactoring will be made possible.
Because this tutorial primarily regards the design of the resource manager, I will not provide implementation details beyond the header declarations; however, the full source can be found on my GitHub repository.
Linux Resource Limitation and Usage Specifications
Before designing a C++ wrapper, we must begin by noting some of the nuances in the Linux specification. In particular, the system calls associated with resource limitations and usage can be found in the Linux manual pages as getrlimit(), setrlimit() and getrusage(). The manual pages describe them as follows:
The
getrlimit()andsetrlimit()system calls get and set resource limits respectively. Each resource has an associated soft and hard limit, as defined by therlimitstructure.
getrusage()returns resource usage measures forwho, which can be one of the following:RUSAGE_SELF,RUSAGE_CHILDREN,RUSAGE_THREAD. The resource usages are returned in the structure pointed to byusage.
Although these system calls are relatively straightforward, they also require a few constants and structures: rlimit and usage. The constants such as RLIMIT_CPU simply define the type of resource to limit; these constants can simply be referred to in the manual. The structures are defined as follows:
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
struct rusage {
struct timeval ru_utime; /* user CPU time used */
struct timeval ru_stime; /* system CPU time used */
long ru_maxrss; /* maximum resident set size */
long ru_ixrss; /* integral shared memory size */
long ru_idrss; /* integral unshared data size */
long ru_isrss; /* integral unshared stack size */
long ru_minflt; /* page reclaims (soft page faults) */
long ru_majflt; /* page faults (hard page faults) */
long ru_nswap; /* swaps */
long ru_inblock; /* block input operations */
long ru_oublock; /* block output operations */
long ru_msgsnd; /* IPC messages sent */
long ru_msgrcv; /* IPC messages received */
long ru_nsignals; /* signals received */
long ru_nvcsw; /* voluntary context switches */
long ru_nivcsw; /* involuntary context switches */
};
// necessary for struct rusage
struct timeval {
time_t tv_sec /* seconds */
suseconds_t tv_usec /* microseconds */
};
These structures can easily be transformed into classes. However, before we design the resource manager, let us describe the difference in programming styles that we expect.
Programming Styles
These structures along with the aforementioned system calls entails a procedural style of resource management. Consider the following procedural code:
struct rlimit cpu;
cpu.rlim_cur = 3;
cpu.rlim_max = 3;
setrlimit(RLIMIT_CPU, &cpu, NULL);
This takes quite a few lines that is often easily reduced to a single line by an OOP style:
ResourceManager.set_resource_limit(ResourceLimit(RLIMIT_CPU, 3, 3));
Although obviously longer as a single line, this does not logically conflict with an OOP programming paradigm. There is no performance optimization by changing the style to OOP; however, placing the code into a wrapper enables us to handle the code as a cross-cutting concern and therefore enable refactoring when the time comes. Recall that the primary benefit is making use of design patterns that OOP offers.
The Structural Design
Before we implement this wrapper, we must consider the high-level design. Allow us to begin with a driver program as our test case which will outline how we would like to use the interface. For simplicity, we will use main as our driver.
int main(int argc, char* argv[])
{
// Acquire a single instance of the manager
ResourceManager& manager = ResourceManager::instance();
// Set resource limits
manager.set_resource_limit(ResourceLimit(RLIMIT_CPU, 3, 3));
manager.set_resource_limit(ResourceLimit(RLIMIT_RTTIME, 3, 3));
manager.apply_limits();
// Do some computation?
fib(31);
// Obtain a resource usage report
ResourceUsage usage = manager.get_resource_usage(RUSAGE_SELF);
// Print the resource usage report
cout << "User Time (sec): " << usage.utime().seconds << endl;
cout << "User Time (usec): " << usage.utime().microseconds << endl;
cout << "System Time (sec): " << usage.stime().seconds << endl;
cout << "System Time (usec): " << usage.stime().microseconds << endl;
return 0;
}
Alright, so we have shown how we would like to use this interface. We will now consider the objects in use.
ResourceManager- Service that applies and tracks our resource limits.
ResourceLimit- Descriptor of a resource limit.
ResourceUsage- Descriptor of a resource usage report.
We must now consider a few nuances to our objects. First, we will look at the ResourceManager. In particular, it is only logical to have one, global instance of a ResourceManager because it applies to the entire process. Consequently, we should use the Singleton pattern. Further, because the manager tracks the currently applied resource limits and there exists a finite (and uniquely identifiable) set of resource limits, we will use a std::set data structure provided by STL.
The singleton design pattern ensures that only one instance of a class is instantiated throughout the lifetime of a program.
Since ResourceLimit and ResourceUsage are simply descriptors, it is easy enough to implement them as straightforward classes. Operationally, ResourceLimit should be able to apply() itself. ResourceUsage should be immutable because its data is consistently changing relative to the speed of our processor; consequently, it is not easily observable.
Now that we have outlined the structure, we will begin implementing the entire component.
Descriptor Design
We will begin with the obvious, descriptor implementation. Specifically, we will begin with the ResourceLimit implementation. The header file should proceed as follows:
#include "sys/resource.h"
class ResourceLimit
{
public:
// constructors
ResourceLimit(int);
ResourceLimit(int, rlim_t, rlim_t);
ResourceLimit(int, rlimit&);
// accessors
rlim_t soft_limit() const;
rlim_t hard_limit() const;
// native accessors
const rlimit& to_rlimit() const;
// operational functions
void get_limit();
void apply();
// comparisons
bool operator==(const ResourceLimit&) const;
bool operator<(const ResourceLimit&) const;
private:
int resource_id_;
rlimit limit_;
bool applied_;
};
As we can see, the necessary accessors are provided and the system-provided rlimit structure is hidden within the class. Further, the comparison operators are provided for when the class is used in the ResourceManager's set.
Before we begin designing the ResourceUsage class, we must define a structure to replace the system-provided timeval structure.
/**
* An alternative representation of timevals
*/
class TimeParts
{
public:
long seconds;
long microseconds;
// constructors
TimeParts(long seconds, long microseconds) :
seconds(seconds), microseconds(microseconds) {}
TimeParts(timeval tv) :
seconds(tv.tv_sec), microseconds(tv.tv_usec) {}
};
Now that we have redefined a class for timeval, we begin the ResourceUsage class which is, of course, immutable.
Immutable objects are non-modifiable after creation. They are necessary for implementing systems in which side effects are circumvented.
#include "sys/resource.h"
#include "sys/time.h"
/**
* A descriptor of the resource usage of the
* current process. This object is immutable
* after construction.
*/
class ResourceUsage
{
public:
// constructors
ResourceUsage(int);
// time accessors
TimeParts utime() const;
TimeParts stime() const;
// accessors
long maxrss() const;
long ixrss() const;
long idrss() const;
long isrss() const;
long minflt() const;
long majflt() const;
long nswap() const;
long inblock() const;
long oublock() const;
long msgsnd() const;
long msgrcv() const;
long nsignals() const;
long nvcsw() const;
long nivcsw() const;
private:
int who_;
rusage usage_;
};
This descriptor also directly models the structure provided by the system; however, note that this design enables absolute immutability. With C-style structures, the data fields would be modifiable which is absolutely unnecessary.
Now that we have designed the descriptors, we can proceed with the design of the manager.
Resource Manager Design
We begin by noting that that the ResourceManager must obviously have access to the ResourceLimit and ResourceUsage descriptors and begin by including their headers into the source. Furthermore, we include the necessary resource management headers provided by the system and the set data structure provided by STL as we have intended.
#include "ResourceLimit.h"
#include "ResourceUsage.h"
#include "Singleton.h"
#include "sys/resource.h"
#include "sys/time.h"
#include <set>
Next, we design the manager to provide the same operational functionality that the Linux system-calls do; however, we design it in the context of sets of resource limits.
class ResourceManager
{
public:
// mutators
bool set_resource_limit(const ResourceLimit&);
// accessors
ResourceLimit get_resource_limit(int) const;
ResourceUsage get_resource_usage(int) const;
// operational functions
void apply_limits();
private:
std::set<ResourceLimit> resource_limits_;
};
This manager enables us to set defer resource limit application until necessary. Additionally, because we use a set data structure, no duplicate resource limits will overwrite each other. Now, we are only missing a single feature: the singleton.
Singleton Application
Although we can use the standard approach by manually embedding the operational functionality of a singleton into our ResourceManager, we can use a modern C++ idiom, the curiously recursive template pattern (CRTP). Specifically, the singleton will have a template parameter which will be our derived class. The singleton will then inject the singleton operational functionality into the derived class.
/**
* Guarantees that only a single instance of an object will exist
* throughout the lifetime of the program.
*/
template <class Derived>
class Singleton
{
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Derived& instance()
{
if (instance_ == nullptr)
instance_ = new Derived();
return *instance_;
}
protected:
Singleton() {}
static Derived* instance_;
};
template <class Derived>
Derived* Singleton::instance_ = nullptr;
Here, notice that the singleton also uses some C++11 features where we delete the copy constructor and the copy assignment operator. Furthermore, notice that singleton operational functionality can now simply be inherited.
Finally, we can then inherit from our singleton,
class ResourceManager : public Singleton<ResourceManager>
{
public:
// mutators
bool set_resource_limit(const ResourceLimit&);
// accessors
ResourceLimit get_resource_limit(int) const;
ResourceUsage get_resource_usage(int) const;
// operational functions
void apply_limits();
private:
std::set<ResourceLimit> resource_limits_;
};
Note that it will be even easier to implement further singleton functionality if necessary; however, this component no further singletons.
At this point, our design is complete! Do not forget that the full source code is available in my GitHub repository.
Conclusion
It is easy to see that there was much duplication in the descriptor classes; however, it is a necessary evil to provide further constraints that classes provide such as immutability. Furthermore, use of the constructor enables quick initialization of objects as we have seen the driver.
Furthermore, utilizing OOP in C++ enables use of design patterns such as the singleton through CRTP. This allows us to design the resource manager as a cross-cutting concern and think of it as a logical abstraction rather than as an implementation detail.