mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 11:27:29 +00:00
parent
ec5dbe66db
commit
3e8cd442a3
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* \brief Sync-session capability type
|
||||
* \author Martin Stein
|
||||
* \date 2015-04-07
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _SYNC_SESSION__CAPABILITY_H_
|
||||
#define _SYNC_SESSION__CAPABILITY_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/capability.h>
|
||||
|
||||
/* local includes */
|
||||
#include <sync_session/sync_session.h>
|
||||
|
||||
namespace Sync
|
||||
{
|
||||
using Genode::Capability;
|
||||
|
||||
typedef Capability<Session> Session_capability;
|
||||
}
|
||||
|
||||
#endif /* _SYNC_SESSION__CAPABILITY_H_ */
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* \brief Client-side Sync-session interface
|
||||
* \author Martin Stein
|
||||
* \date 2015-04-07
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _SYNC_SESSION__CLIENT_H_
|
||||
#define _SYNC_SESSION__CLIENT_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/rpc_client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <sync_session/capability.h>
|
||||
|
||||
namespace Sync
|
||||
{
|
||||
using Genode::Rpc_client;
|
||||
|
||||
struct Session_client;
|
||||
}
|
||||
|
||||
|
||||
struct Sync::Session_client : Rpc_client<Session>
|
||||
{
|
||||
explicit Session_client(Session_capability session)
|
||||
: Rpc_client<Session>(session) { }
|
||||
|
||||
void threshold(unsigned id, unsigned threshold) override {
|
||||
call<Rpc_threshold>(id, threshold); }
|
||||
|
||||
void submit(unsigned id, Signal_context_capability sigc) override {
|
||||
call<Rpc_submit>(id, sigc); }
|
||||
};
|
||||
|
||||
#endif /* _SYNC_SESSION__CLIENT_H_ */
|
@ -16,45 +16,22 @@
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/connection.h>
|
||||
#include <base/rpc_client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <sync_session/client.h>
|
||||
#include <sync_session/sync_session.h>
|
||||
|
||||
namespace Sync
|
||||
namespace Sync { class Connection; }
|
||||
|
||||
struct Sync::Connection : public Genode::Connection<Session>,
|
||||
public Genode::Rpc_client<Session>
|
||||
{
|
||||
using Genode::Parent;
|
||||
explicit Connection(Genode::Env &env)
|
||||
: Genode::Connection<Session>(env, session("ram_quota=4K")),
|
||||
Genode::Rpc_client<Session>(cap()) { }
|
||||
|
||||
class Connection;
|
||||
}
|
||||
|
||||
|
||||
class Sync::Connection : public Genode::Connection<Session>,
|
||||
public Session_client
|
||||
{
|
||||
public:
|
||||
|
||||
class Connection_failed : public Parent::Exception { };
|
||||
|
||||
private:
|
||||
|
||||
Session_capability _create_session()
|
||||
{
|
||||
try { return session("ram_quota=4K"); }
|
||||
catch (...) { throw Connection_failed(); }
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* \throw Connection_failed
|
||||
*/
|
||||
Connection() __attribute__((deprecated))
|
||||
:
|
||||
Genode::Connection<Session>(_create_session()),
|
||||
Session_client(cap())
|
||||
{ }
|
||||
void threshold(unsigned threshold) override { call<Rpc_threshold>(threshold); }
|
||||
void submit(Signal_context_capability signal) override { call<Rpc_submit>(signal); }
|
||||
};
|
||||
|
||||
#endif /* _SYNC_SESSION__CONNECTION_H_ */
|
||||
|
@ -15,6 +15,7 @@
|
||||
#define _SYNC_SESSION__SYNC_SESSION_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/capability.h>
|
||||
#include <session/session.h>
|
||||
#include <base/signal.h>
|
||||
|
||||
@ -23,32 +24,20 @@ namespace Sync
|
||||
using Genode::Signal_context_capability;
|
||||
|
||||
struct Session;
|
||||
using Session_capability = Genode::Capability<Session>;
|
||||
}
|
||||
|
||||
|
||||
struct Sync::Session : Genode::Session
|
||||
{
|
||||
static const char *service_name() { return "Sync"; }
|
||||
|
||||
virtual ~Session() { }
|
||||
|
||||
/**
|
||||
* Set the submission threshold of a synchronization signal
|
||||
*/
|
||||
virtual void threshold(unsigned id, unsigned threshold) = 0;
|
||||
virtual void threshold(unsigned threshold) = 0;
|
||||
virtual void submit(Signal_context_capability signal) = 0;
|
||||
|
||||
/**
|
||||
* Submit to a synchronization signal
|
||||
*/
|
||||
virtual void submit(unsigned id, Signal_context_capability sigc) = 0;
|
||||
|
||||
|
||||
/*********************
|
||||
** RPC declaration **
|
||||
*********************/
|
||||
|
||||
GENODE_RPC(Rpc_threshold, void, threshold, unsigned, unsigned);
|
||||
GENODE_RPC(Rpc_submit, void, submit, unsigned, Signal_context_capability);
|
||||
GENODE_RPC(Rpc_threshold, void, threshold, unsigned);
|
||||
GENODE_RPC(Rpc_submit, void, submit, Signal_context_capability);
|
||||
|
||||
GENODE_RPC_INTERFACE(Rpc_threshold, Rpc_submit);
|
||||
};
|
||||
|
@ -12,166 +12,150 @@
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/log.h>
|
||||
#include <base/thread.h>
|
||||
#include <base/component.h>
|
||||
#include <base/sleep.h>
|
||||
#include <timer_session/connection.h>
|
||||
|
||||
/* local includes */
|
||||
#include <sync_session/connection.h>
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
enum { SYNC_SIG = 0 };
|
||||
|
||||
namespace Sync { class Signal; }
|
||||
|
||||
class Single_signal
|
||||
struct Single_signal
|
||||
{
|
||||
private:
|
||||
Signal_receiver receiver;
|
||||
Signal_context context;
|
||||
Signal_context_capability cap;
|
||||
Signal_transmitter transmitter;
|
||||
|
||||
Signal_receiver _sigr;
|
||||
Signal_context _sigx;
|
||||
Signal_context_capability _sigc;
|
||||
Signal_transmitter _sigt;
|
||||
Single_signal() : cap(receiver.manage(&context)), transmitter(cap) { }
|
||||
|
||||
public:
|
||||
|
||||
Single_signal() : _sigc(_sigr.manage(&_sigx)), _sigt(_sigc) { }
|
||||
|
||||
~Single_signal() { _sigr.dissolve(&_sigx); }
|
||||
|
||||
void receive() { _sigr.wait_for_signal(); }
|
||||
|
||||
void submit() { _sigt.submit(); }
|
||||
|
||||
operator Signal_context_capability() { return _sigc; }
|
||||
~Single_signal() { receiver.dissolve(&context); }
|
||||
void receive() { receiver.wait_for_signal(); }
|
||||
void submit() { transmitter.submit(); }
|
||||
};
|
||||
|
||||
class Sync::Signal
|
||||
|
||||
struct Synchronizer
|
||||
{
|
||||
private:
|
||||
Single_signal signal;
|
||||
Sync::Session &session;
|
||||
|
||||
Signal_receiver _sigr;
|
||||
Signal_context _sigx;
|
||||
Signal_context_capability _sigc;
|
||||
Session * const _session;
|
||||
unsigned const _id;
|
||||
Synchronizer(Sync::Session &session) : session(session) { }
|
||||
|
||||
public:
|
||||
void threshold(unsigned threshold) { session.threshold(threshold); }
|
||||
|
||||
Signal(Session * const session, unsigned const id)
|
||||
: _sigc(_sigr.manage(&_sigx)), _session(session), _id(id) { }
|
||||
|
||||
~Signal() { _sigr.dissolve(&_sigx); }
|
||||
|
||||
void threshold(unsigned const threshold) {
|
||||
_session->threshold(_id, threshold); }
|
||||
|
||||
void sync()
|
||||
{
|
||||
_session->submit(_id, _sigc);
|
||||
_sigr.wait_for_signal();
|
||||
}
|
||||
void synchronize()
|
||||
{
|
||||
session.submit(signal.cap);
|
||||
signal.receive();
|
||||
}
|
||||
};
|
||||
|
||||
class Counter : private Thread_deprecated<2 * 1024 * sizeof(Genode::addr_t)>
|
||||
|
||||
class Counter : public Thread
|
||||
{
|
||||
private:
|
||||
|
||||
String<64> _name;
|
||||
unsigned long long volatile _value;
|
||||
Sync::Signal _sync_sig;
|
||||
unsigned volatile _stage;
|
||||
Single_signal _stage_1_end;
|
||||
Single_signal _stage_2_reached;
|
||||
enum { STACK_SIZE = 2 * 1024 * sizeof(addr_t) };
|
||||
|
||||
inline void _stage_0_and_1(unsigned long long volatile & value)
|
||||
{
|
||||
_stage_1_end.receive();
|
||||
_stage = 0;
|
||||
_sync_sig.sync();
|
||||
while(_stage == 0) { value++; }
|
||||
}
|
||||
enum Stage { PAUSE, MEASUREMENT, DESTRUCTION };
|
||||
|
||||
Name const &_name;
|
||||
unsigned long long volatile _value { 0 };
|
||||
Stage volatile _stage { PAUSE };
|
||||
Single_signal _start_measurement;
|
||||
Single_signal _start_destruction;
|
||||
Synchronizer _synchronizer;
|
||||
|
||||
void entry()
|
||||
{
|
||||
unsigned long long volatile value = 0;
|
||||
while (_stage < 2) { _stage_0_and_1(value); }
|
||||
while (_stage == PAUSE) {
|
||||
_start_measurement.receive();
|
||||
_stage = MEASUREMENT;
|
||||
_synchronizer.synchronize();
|
||||
while (_stage == MEASUREMENT) { value++; }
|
||||
}
|
||||
_value = value;
|
||||
_stage_2_reached.submit();
|
||||
sleep_forever();
|
||||
_start_destruction.submit();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Counter(char const *name, size_t const weight,
|
||||
Sync::Session * const sync)
|
||||
Counter(Env &env, Name const &name, unsigned cpu_percent, Sync::Session &sync)
|
||||
:
|
||||
Thread_deprecated(weight, "counter"), _name(name), _value(0) ,
|
||||
_sync_sig(sync, SYNC_SIG), _stage(1)
|
||||
{
|
||||
Thread::start();
|
||||
}
|
||||
Thread(env, name, STACK_SIZE, Location(),
|
||||
Weight(Cpu_session::quota_lim_upscale(cpu_percent, 100)),
|
||||
env.cpu()),
|
||||
_name(name), _synchronizer(sync) { start(); }
|
||||
|
||||
void destruct()
|
||||
{
|
||||
_stage = 2;
|
||||
_stage_2_reached.receive();
|
||||
_stage = DESTRUCTION;
|
||||
_start_destruction.receive();
|
||||
this->~Counter();
|
||||
}
|
||||
|
||||
void pause() { _stage = 1; }
|
||||
void pause() { _stage = PAUSE; }
|
||||
void measure() { _start_measurement.submit(); }
|
||||
|
||||
void go() { _stage_1_end.submit(); }
|
||||
|
||||
void result() { log("counter ", _name, " ", _value); }
|
||||
void print(Output &output) const { Genode::print(output, _name, " ", _value); }
|
||||
};
|
||||
|
||||
|
||||
void measure(Timer::Connection & timer, Single_signal & timer_sig,
|
||||
Sync::Signal & sync_sig, unsigned const sec)
|
||||
struct Main
|
||||
{
|
||||
timer.trigger_once(sec * 1000 * 1000);
|
||||
sync_sig.sync();
|
||||
timer_sig.receive();
|
||||
}
|
||||
enum { DURATION_BASE_SEC = 20,
|
||||
MEASUREMENT_1_NR_OF_THREADS = 9,
|
||||
MEASUREMENT_2_NR_OF_THREADS = 6,
|
||||
CONCLUSION_NR_OF_THREADS = 3, };
|
||||
|
||||
Env &env;
|
||||
Single_signal timer_signal;
|
||||
Timer::Connection timer { env };
|
||||
Sync::Connection sync { env };
|
||||
Synchronizer synchronizer { sync };
|
||||
Counter::Name const name_a { "counter A" };
|
||||
Counter::Name const name_b { "counter B" };
|
||||
Counter counter_a { env, name_a, 10, sync };
|
||||
Counter counter_b { env, name_b, 90, sync };
|
||||
|
||||
Main(Env &env) : env(env)
|
||||
{
|
||||
timer.sigh(timer_signal.cap);
|
||||
|
||||
auto measure = [&] (unsigned duration_sec) {
|
||||
timer.trigger_once(duration_sec * 1000 * 1000);
|
||||
synchronizer.synchronize();
|
||||
timer_signal.receive();
|
||||
};
|
||||
/* measurement 1 */
|
||||
synchronizer.threshold(MEASUREMENT_1_NR_OF_THREADS);
|
||||
counter_a.measure();
|
||||
counter_b.measure();
|
||||
measure(3 * DURATION_BASE_SEC);
|
||||
counter_a.pause();
|
||||
counter_b.destruct();
|
||||
|
||||
/* measurement 2 */
|
||||
synchronizer.threshold(MEASUREMENT_2_NR_OF_THREADS);
|
||||
counter_a.measure();
|
||||
measure(DURATION_BASE_SEC);
|
||||
counter_a.destruct();
|
||||
|
||||
/* conclusion */
|
||||
synchronizer.threshold(CONCLUSION_NR_OF_THREADS);
|
||||
synchronizer.synchronize();
|
||||
Cpu_session::Quota quota = env.cpu().quota();
|
||||
log("quota super period ", quota.super_period_us);
|
||||
log("quota ", quota.us);
|
||||
log(counter_a);
|
||||
log(counter_b);
|
||||
log("done");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
enum { DURATION_BASE_SEC = 20 };
|
||||
|
||||
/* prepare */
|
||||
Single_signal timer_sig;
|
||||
Timer::Connection timer;
|
||||
Sync::Connection sync;
|
||||
Sync::Signal sync_sig(&sync, SYNC_SIG);
|
||||
Counter counter_a("A", Cpu_session::quota_lim_upscale(10, 100), &sync);
|
||||
Counter counter_b("B", Cpu_session::quota_lim_upscale(90, 100), &sync);
|
||||
|
||||
timer.sigh(timer_sig);
|
||||
|
||||
/* measure stage 1 */
|
||||
sync_sig.threshold(9);
|
||||
counter_a.go();
|
||||
counter_b.go();
|
||||
measure(timer, timer_sig, sync_sig, 3 * DURATION_BASE_SEC);
|
||||
counter_a.pause();
|
||||
counter_b.destruct();
|
||||
|
||||
/* measure stage 2 */
|
||||
sync_sig.threshold(6);
|
||||
counter_a.go();
|
||||
measure(timer, timer_sig, sync_sig, DURATION_BASE_SEC);
|
||||
counter_a.destruct();
|
||||
|
||||
/* print results */
|
||||
sync_sig.threshold(3);
|
||||
sync_sig.sync();
|
||||
Cpu_session::Quota quota = Genode::env()->cpu_session()->quota();
|
||||
log("quota super period ", quota.super_period_us);
|
||||
log("quota ", quota.us);
|
||||
counter_a.result();
|
||||
counter_b.result();
|
||||
log("done");
|
||||
sleep_forever();
|
||||
}
|
||||
void Component::construct(Env &env) { static Main main(env); }
|
||||
|
@ -14,139 +14,76 @@
|
||||
/* Genode includes */
|
||||
#include <os/server.h>
|
||||
#include <root/component.h>
|
||||
#include <base/heap.h>
|
||||
#include <base/component.h>
|
||||
|
||||
/* local includes */
|
||||
#include <sync_session/connection.h>
|
||||
|
||||
namespace Sync
|
||||
using namespace Genode;
|
||||
|
||||
class Sync_root;
|
||||
|
||||
|
||||
struct Session_component : public Rpc_object<Sync::Session>
|
||||
{
|
||||
enum { NR_OF_SIGNALS = 1 };
|
||||
Sync_root &root;
|
||||
|
||||
using Server::Entrypoint;
|
||||
using Genode::Rpc_object;
|
||||
using Genode::env;
|
||||
using Genode::Root_component;
|
||||
using Genode::Allocator;
|
||||
using Genode::Signal_transmitter;
|
||||
Session_component(Sync_root &root) : root(root) { }
|
||||
|
||||
class Signal;
|
||||
class Session_component;
|
||||
class Root;
|
||||
struct Main;
|
||||
void threshold(unsigned threshold) override;
|
||||
void submit(Signal_context_capability signal) override;
|
||||
};
|
||||
|
||||
|
||||
struct Sync_root : public Root_component<Session_component>
|
||||
{
|
||||
Signal_transmitter transmitters[9];
|
||||
unsigned submitted { 0 };
|
||||
unsigned threshold { 0 };
|
||||
|
||||
void check()
|
||||
{
|
||||
if (submitted < threshold) { return; }
|
||||
for (unsigned i = 0; i < submitted; i++) {
|
||||
transmitters[i].submit(); }
|
||||
submitted = 0;
|
||||
}
|
||||
|
||||
Session_component *_create_session(char const *args) override
|
||||
{
|
||||
try { return new (md_alloc()) Session_component(*this); }
|
||||
catch (...) { throw Root::Exception(); }
|
||||
}
|
||||
|
||||
Sync_root(Entrypoint &ep, Allocator &md_alloc)
|
||||
: Root_component<Session_component>(ep, md_alloc) { }
|
||||
};
|
||||
|
||||
|
||||
void Session_component::threshold(unsigned threshold)
|
||||
{
|
||||
root.threshold = threshold;
|
||||
root.check();
|
||||
}
|
||||
|
||||
class Sync::Signal
|
||||
|
||||
void Session_component::submit(Signal_context_capability signal)
|
||||
{
|
||||
friend class Root;
|
||||
|
||||
private:
|
||||
|
||||
enum { NR_OF_TRANSMITTERS = 9 };
|
||||
|
||||
Signal_transmitter _transmitters[NR_OF_TRANSMITTERS];
|
||||
unsigned _submitted;
|
||||
unsigned _threshold;
|
||||
|
||||
void _check()
|
||||
{
|
||||
if (_submitted < _threshold) { return; }
|
||||
for (unsigned i = 0; i < _submitted; i++) {
|
||||
_transmitters[i].submit(); }
|
||||
_submitted = 0;
|
||||
}
|
||||
|
||||
void _reset()
|
||||
{
|
||||
_submitted = 0;
|
||||
_threshold = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void threshold(unsigned const threshold)
|
||||
{
|
||||
_threshold = threshold;
|
||||
_check();
|
||||
}
|
||||
|
||||
void submit(Signal_context_capability & sigc)
|
||||
{
|
||||
_transmitters[_submitted] = Signal_transmitter(sigc);
|
||||
_submitted++;
|
||||
_check();
|
||||
}
|
||||
};
|
||||
|
||||
class Sync::Session_component : public Rpc_object<Session>
|
||||
{
|
||||
private:
|
||||
|
||||
Signal * const _signals;
|
||||
|
||||
public:
|
||||
|
||||
Session_component(Signal * const signals) : _signals(signals) { }
|
||||
|
||||
void threshold(unsigned id, unsigned threshold) override
|
||||
{
|
||||
if (id >= NR_OF_SIGNALS) { return; }
|
||||
_signals[id].threshold(threshold);
|
||||
}
|
||||
|
||||
void
|
||||
submit(unsigned const id, Signal_context_capability sigc) override
|
||||
{
|
||||
if (id >= NR_OF_SIGNALS) { return; }
|
||||
_signals[id].submit(sigc);
|
||||
}
|
||||
};
|
||||
|
||||
class Sync::Root : public Root_component<Session_component>
|
||||
{
|
||||
private:
|
||||
|
||||
Signal _signals[NR_OF_SIGNALS];
|
||||
|
||||
protected:
|
||||
|
||||
Session_component *_create_session(const char *args)
|
||||
{
|
||||
try { return new (md_alloc()) Session_component(_signals); }
|
||||
catch (...) { throw Root::Exception(); }
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Root(Entrypoint & ep, Allocator & md_alloc)
|
||||
: Root_component<Session_component>(&ep.rpc_ep(), &md_alloc)
|
||||
{
|
||||
for (unsigned i = 0; i < NR_OF_SIGNALS; i++) {
|
||||
_signals[i]._reset(); }
|
||||
}
|
||||
};
|
||||
|
||||
struct Sync::Main
|
||||
{
|
||||
Server::Entrypoint & ep;
|
||||
|
||||
Root root;
|
||||
|
||||
Main(Server::Entrypoint & ep) : ep(ep), root(ep, *env()->heap()) {
|
||||
env()->parent()->announce(ep.manage(root)); }
|
||||
};
|
||||
|
||||
|
||||
/************
|
||||
** Server **
|
||||
************/
|
||||
|
||||
namespace Server
|
||||
{
|
||||
using namespace Sync;
|
||||
|
||||
char const *name() { return "sync_ep"; }
|
||||
|
||||
size_t stack_size() { return 16*1024*sizeof(long); }
|
||||
|
||||
void construct(Entrypoint & ep) { static Main main(ep); }
|
||||
root.transmitters[root.submitted] = Signal_transmitter(signal);
|
||||
root.submitted++;
|
||||
root.check();
|
||||
}
|
||||
|
||||
|
||||
struct Main
|
||||
{
|
||||
Env &env;
|
||||
Heap heap { env.ram(), env.rm() };
|
||||
Sync_root root { env.ep(), heap };
|
||||
|
||||
Main(Env &env) : env(env) { env.parent().announce(env.ep().manage(root)); }
|
||||
};
|
||||
|
||||
|
||||
void Component::construct(Env &env) { static Main main(env); }
|
||||
|
@ -1,17 +1,4 @@
|
||||
#
|
||||
# \brief Provide cross-component synchronization
|
||||
# \author Martin Stein
|
||||
# \date 2014-10-13
|
||||
#
|
||||
|
||||
# Set program name
|
||||
TARGET = test-sync
|
||||
|
||||
# Add C++ sources
|
||||
SRC_CC = main.cc
|
||||
|
||||
# Add include paths
|
||||
TARGET = test-sync
|
||||
SRC_CC += main.cc
|
||||
INC_DIR += $(PRG_DIR)/../include
|
||||
|
||||
# Add libraries
|
||||
LIBS = base server
|
||||
LIBS += base
|
||||
|
@ -1,17 +1,4 @@
|
||||
#
|
||||
# \brief Test the distribution and application of CPU quota
|
||||
# \author Martin Stein
|
||||
# \date 2014-10-13
|
||||
#
|
||||
|
||||
# Set program name
|
||||
TARGET = test-cpu_quota
|
||||
|
||||
# Add C++ sources
|
||||
SRC_CC += main.cc
|
||||
|
||||
# Add include paths
|
||||
TARGET = test-cpu_quota
|
||||
SRC_CC += main.cc
|
||||
INC_DIR += $(PRG_DIR)/include
|
||||
|
||||
# Add libraries
|
||||
LIBS += base
|
||||
LIBS += base
|
||||
|
Loading…
x
Reference in New Issue
Block a user