From patchwork Mon Aug 13 18:46:46 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 177024 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id C82E72C0091 for ; Tue, 14 Aug 2012 04:47:08 +1000 (EST) Received: from localhost ([::1]:57139 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T0zfO-0006aw-V0 for incoming@patchwork.ozlabs.org; Mon, 13 Aug 2012 14:47:06 -0400 Received: from eggs.gnu.org ([208.118.235.92]:55883) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T0zfC-0006aV-N0 for qemu-devel@nongnu.org; Mon, 13 Aug 2012 14:46:58 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1T0zfA-00024O-JM for qemu-devel@nongnu.org; Mon, 13 Aug 2012 14:46:54 -0400 Received: from cpe-70-123-145-39.austin.res.rr.com ([70.123.145.39]:56715 helo=localhost6.localdomain6) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T0zfA-00023X-9q for qemu-devel@nongnu.org; Mon, 13 Aug 2012 14:46:52 -0400 Received: from localhost6.localdomain6 (localhost.localdomain [127.0.0.1]) by localhost6.localdomain6 (8.14.4/8.14.4/Debian-2ubuntu2) with ESMTP id q7DIkmDg014889; Mon, 13 Aug 2012 13:46:48 -0500 Received: (from anthony@localhost) by localhost6.localdomain6 (8.14.4/8.14.4/Submit) id q7DIkkKG014888; Mon, 13 Aug 2012 13:46:46 -0500 From: Anthony Liguori To: qemu-devel@nongnu.org Date: Mon, 13 Aug 2012 13:46:46 -0500 Message-Id: <1344883606-14854-1-git-send-email-aliguori@us.ibm.com> X-Mailer: git-send-email 1.7.5.4 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 70.123.145.39 Cc: Peter Maydell , Paolo Bonzini , Anthony Liguori , Andreas Faerber , Michael Tsirkin Subject: [Qemu-devel] [PATCH] qom: add style guide X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Signed-off-by: Anthony Liguori --- docs/qom-style-guide.md | 489 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 489 insertions(+), 0 deletions(-) create mode 100644 docs/qom-style-guide.md diff --git a/docs/qom-style-guide.md b/docs/qom-style-guide.md new file mode 100644 index 0000000..e7590e0 --- /dev/null +++ b/docs/qom-style-guide.md @@ -0,0 +1,489 @@ +QEMU Object Model Style Guide +============================= + +Overview +-------- +This document is a step-by-step tutorial of QOM. It is meant to be read from +top to bottom addressing the most common use-cases at the start. This is a +living document and contributions are welcome but it should not attempt to be +an API reference. There code contains inline documentation and API details +should be covered in the respective header files. + +Motivation +---------- +QEMU makes extensive use of object oriented programming. Since QEMU is written +in C, these OOP concepts often use very different mechanisms to achieve the +same goal. The net effect is a lot of infrastructure duplication and a general +lack of consistency. + +The goal of QOM is to use a single infrastructure for all OOP within QEMU. This +improves consistency and eases maintainability over the long term. + +QOM provides a common infrastructure for: + +- Type Management + - Registering types + - Enumerating registered types +- Inheritence + - Single-parent inheritance + - Introspection of inheritance hierarchy + - Multiple inheritance through stateless interfaces +- Polymorphism + - Class based polymorphism + - Virtual and pure virtual methods + - Constructor/destructor chaining +- Object Properties + - Dynamic property registration (tied to Objects) + - Property introspection + - Access permissions + - Accessor hooks +- Type Casting + - Runtime checked upcast/downcast + - Full support for casting up and down the chain (including interfaces) +- Object Enumeration + - Expression of relationship between objects + - Ability to reference objects with a symbolic path + - Represented as a directed graph + +While QOM has a lot of high level concepts, the primary design goal has been to +keep simple concepts simple to implement. + +A Note on Consistency +--------------------- +Much of the QOM types in QEMU have been converted through automated scripts. +Tremendous effort was made to make sure the resulting code was high quality and +adhered to all of the guidelines in this document. However, there are many +cases where some of the conversion could not be scripted easily and some short +cuts where taken. + +Whenever possible, as code is refactored for other reasons, it should be brought +up fully to the guidelines expressed in this document. + +Creating a Simple Type +---------------------- +The easiest way to understand QOM is to walk through an example. This is a +typical example of creating a new type derived from Object as the parent class. +In this example, all code would live in a single C source file. + +Let's get started: + + #include "qemu/object.h" + +This is the header file that contains the core QOM infrastructure. It has +minimum dependencies to facilitate unit testing. + + #define TYPE_MY_TYPE "my-type" + #define MY_TYPE(obj) OBJECT_CHECK(MyType, (obj), TYPE_MY_TYPE) + +All QOM types should define at least two macros. The first macro is a symbolic +version of the type name. It should always take the form +TYPE_ + upper(typename). Type names should generally follow the naming rules of +QAPI which means dashes, '-', are preferred to underscores, '_'. + +The second macro is a cast macro. The first argument is the type struct and the +remaining arguments are self-evident. This form should always be followed even +if the cast macro isn't currently used by the C file. + + typedef struct MyType MyType; + + struct MyType + { + Object parent_obj; + + /*< private >*/ + int foo; + }; + +When declaring the structure, a forward declaration should be used. This is +useful for consistency sake as it is required when defining classes. + +The first element must be the parent type and should be named 'parent_obj' or +just 'parent'. When working with QOM types, you should avoid ever accessing +this member directly instead relying on casting macros. + +Casting macros hide the inheritence hierarchy from the implementation. This +makes it easier to refactor code over time by changing the hierarchy without +changing the code in many places. + + static TypeInfo my_type_info = { + .name = TYPE_MY_TYPE, + .parent = TYPE_OBJECT, + .instance_size = sizeof(MyType), + }; + + static void register_types(void) + { + type_register_static(&my_type_info); + } + + type_init(register_types); + +All QOM types must be registered with the QOM infrastructure. Once registered, +the user has the ability to enumerate types, create objects, and interact with +objects without any additional code. + +All types must set the 'name' and 'parent' parameters. The type macros should +always be used for these parameters. Almost all types should set the +'instance_size' parameter although if it's not specified, it will be inherited +from its parent. + +Finally, a module init function should be provided. The naming convention +shown here should be used in all new code. + +In general, one C file should register one type. There are many valid +exceptions to this rule but whenever possible, types should be split out into +separate C files. + +Creating a Type with Methods +---------------------------- +The next most common interaction with QOM will be to create a type that will be +inherited from another type. This usually involves adding a class and +implementing a virtual method that can be overridden subclasses. The +following diff shows the changes we would need to extend our previous example to +allow inheritance with polymorphism. + + @@ -1,10 +1,25 @@ + +#ifndef QEMU_MY_TYPE_H + +#define QEMU_MY_TYPE_H + + + #include "qemu/object.h" + +This example assumes that the initial declarations will be split into a +separate header. To simplify the example, guards are used to show where the +header file starts and ends. + + #define TYPE_MY_TYPE "my-type" + #define MY_TYPE(obj) \ + OBJECT_CHECK(MyType, (obj), TYPE_MY_TYPE) + +#define MY_TYPE_CLASS(klass) \ + + OBJECT_CLASS_CHECK(MyTypeClass, (klass), TYPE_MY_TYPE) + +#define MY_TYPE_GET_CLASS(obj) \ + + OBJECT_GET_CLASS(MyTypeClass, (obj), TYPE_MY_TYPE) + +When adding a class, we need to add two more macros to the type definition. The +first macro is a class casting macro. This looks very similar to an object cast +macro but instead takes a class as an argument. + +The second macro that we add allows a user to get a class pointer from an +object. Method dispatch requires this last macro. + + typedef struct MyType MyType; + +typedef struct MyTypeClass MyTypeClass; + + + +struct MyTypeClass + +{ + + ObjectClass parent_klass; + + + + void (*bar)(MyType *obj, int foo); + +}; + +A class looks very similar to an object in that it is expressed as a C structure +and the first member must be the class of the parent type. + +Typically classes will only contain function pointers but it is possible to have +data members of a class. The first argument to each function pointer should +always be the object type. + + struct MyType + { + @@ -14,10 +29,35 @@ struct MyType + int foo; + }; + + +void my_type_bar(MyType *obj, int foo); + + + +#endif + +A helper function should be provided for doing method dispatch. This improves +readability and convenience. + + + + +static void my_type_default_bar(MyType *obj, int foo) + +{ + + /* do nothing */ + +} + + + +void my_type_bar(MyType *obj, int foo) + +{ + + MyTypeClass *mc = MY_TYPE_GET_CLASS(obj); + + + + mc->bar(obj, foo); + +} + + + +static void my_type_class_init(ObjectClass *klass, void *data) + +{ + + MyTypeClass *mc = MY_TYPE_CLASS(klass); + + + + mc->bar = my_type_default_bar; + +} + + + static TypeInfo my_type_info = { + .name = TYPE_MY_TYPE, + .parent = TYPE_OBJECT, + .instance_size = sizeof(MyType), + + .class_size = sizeof(MyTypeClass), + + .class_init = my_type_class_init, + }; + + static void register_types(void) + +In order to add a new class for an type, we need to specific the size of the +class in the TypeInfo. We also need to provide a function to initialize the +class. Classes are only ever created and initialized once for any type so this +function will be called once regardless of how many objects are created of this +type. + +The class init function should follow a naming convention of +typename + '_class_init'. The class init function should cast the klass +parameter to the appropriate type and then overload the methods appropriately. + +In this example, we're initializing the method to a dummy function that does +nothing useful. This is because 'foo' is a virtual method meaning that base +classes do not need to implement the function if they don't want to override +the behavior. + +If we did not initialize the method, the function would be a pure virtual method +meaning that the subclass must implement the function. QOM cannot enforce this +requirement so care should be taken in the wrapper function to check for NULL. + +The wrapper function simply dispatches the method. It should not implement any +logic or behavior beyond just dispatching the method. Checking for NULL and +either returning an error or asserting is acceptable behavior for a wrapper +function. + +Implementing Devices and Overloading Methods +-------------------------------------------- +Most QOM users will not implement objects that derive from TYPE_OBJECT. +Instead, usually QOM users will derive from TYPE_DEVICE or some other base +class and will also have to implement virtual methods. + +In this example, we change MyType to inherit from TYPE_DEVICE and then implement +the required pure virtual method. + + @@ -16,14 +16,14 @@ typedef struct MyTypeClass MyTypeClass; + + struct MyTypeClass + { + - ObjectClass parent_klass; + + DeviceClass parent_klass; + + void (*bar)(MyType *obj, int foo); + }; + + struct MyType + { + - Object parent_obj; + + DeviceState parent_obj; + + /*< private >*/ + int foo; + +Changing the parent type is trivial as it just requires modifying the +structures. This is one of the benefits of doing all casts through the cast +macro. It simplifies the process of refactoring. + + @@ -45,16 +45,27 @@ void my_type_bar(MyType *obj, int foo) + mc->bar(obj, foo); + } + + +static int my_type_realize(DeviceState *dev) + +{ + + MyType *my = MY_TYPE(dev); + + + + my->foo = 1; + + + + return 0; + +} + + + static void my_type_class_init(ObjectClass *klass, void *data) + { + MyTypeClass *mc = MY_TYPE_CLASS(klass); + + DeviceClass *dc = DEVICE_CLASS(klass); + + mc->bar = my_type_default_bar; + + dc->init = my_type_realize; + } + +TYPE_DEVICE has a pure virtual method 'init' which is a bit of a misnomer. The +'init' method is called after construction but before the guest is started for +the first time. In QOM nomenclature, we call this realize. At some point in +time, TYPE_DEVICE will be refactored to rename the init method to realize but +for now, we have to live with this inconsistency. + + static TypeInfo my_type_info = { + .name = TYPE_MY_TYPE, + - .parent = TYPE_OBJECT, + + .parent = TYPE_DEVICE, + .instance_size = sizeof(MyType), + .class_size = sizeof(MyTypeClass), + .class_init = my_type_class_init, + +Using Instance Initialization +----------------------------- + +QDev required all initialization and destruction to happen through 'init' and +'exit' methods. Since QDev didn't have a concept of constructors and +destructors, it was up to the type implementors to implement chaining which +often was done in an inconsistent fashion. + +As part of the TypeInfo structure, QOM has a instance_init and instance_finalize +method which acts as the constructor and destructor respectively. These +functions are called starting with the subclass and working up the type +hierarchy by QOM. + +Any state that can be initialized independently of user supplied state should be +initialized as part of the constructor. + + @@ -33,6 +33,13 @@ void my_type_bar(MyType *obj, int foo); + + #endif + + +static void my_type_initfn(Object *obj) + +{ + + MyType *my = MY_TYPE(obj); + + + + my->foo = 1; + +} + + + static void my_type_default_bar(MyType *obj, int foo) + { + /* do nothing */ + @@ -47,10 +54,6 @@ void my_type_bar(MyType *obj, int foo) + + static int my_type_realize(DeviceState *dev) + { + - MyType *my = MY_TYPE(dev); + - + - my->foo = 1; + - + return 0; + } + + @@ -69,6 +72,7 @@ static TypeInfo my_type_info = { + .instance_size = sizeof(MyType), + .class_size = sizeof(MyTypeClass), + .class_init = my_type_class_init, + + .instance_init = my_type_initfn, + }; + + static void register_types(void) + +Since 'foo' can be initialized without relying on user provided state, we can +move that logic entirely to the constructor. Unfortunately, the DeviceState +init function must remain since it is pure virtual but it is now trivial. + +User Provided State (Properties) +-------------------------------- + +A common property of most objects within QEMU is that there is a desire to +allow users to adjust parameters of the object either during initial creation or +at run time. Properties provide a common framework for doing this. + +Properties are rich and complex and will not be covered exhaustively here. +Refer to the documentation in the qemu/object.h header file for exhaustive +documentation. + +Most interactions with properties will happen through convenience functions +that make adding properties easier for typical users. In the case of our +example, we'll add properties using the qdev static property interface. + + @@ -27,6 +27,7 @@ struct MyType + + /*< private >*/ + int foo; + + int max_vectors; + }; + +With static properties, a specific property corresponds to a member of the +object structure. The infrastructure in qdev may change this member's value +automatically at any time before calling DeviceState::init(). That means any +initialization that depends on members exposed as properties must be done in +the DeviceState::init method. + + void my_type_bar(MyType *obj, int foo); + @@ -54,9 +55,20 @@ void my_type_bar(MyType *obj, int foo) + + static int my_type_realize(DeviceState *dev) + { + + MyType *mt = MY_TYPE(dev); + + + + if (mt->max_vectors > 100) { + + return -EINVAL; + + } + + + return 0; + } + +For this example, we simply validate the property contains a sane value and fail +realize. + + +static Property my_type_properties[] = { + + DEFINE_PROP_INT("max-vectors", MyType, max_vectors, 0), + + DEFINE_PROP_END_OF_LIST(), + +}; + + + static void my_type_class_init(ObjectClass *klass, void *data) + { + MyTypeClass *mc = MY_TYPE_CLASS(klass); + @@ -64,6 +76,7 @@ static void my_type_class_init(ObjectClass *klass, + + mc->bar = my_type_default_bar; + dc->init = my_type_realize; + + dc->props = my_type_properties; + } + + static TypeInfo my_type_info = { + +Static properties are registered using a static class variable in the +TYPE_DEVICE class. Base classes can add static properties using this approach +and subclasses will automatically inherit them. + +Child and Link Properties +------------------------- + +The other common types of properties in QOM are child and link properties. Like +static properties, there are special helpers to add these properties to an +object. + + @@ -25,6 +25,9 @@ struct MyType + { + DeviceState parent_obj; + + + Pin *in; + + Pin out; + + + /*< private >*/ + int foo; + int max_vectors; + +To begin with, we have to add struct members in that object that will hold the +properties. A link is a pointer to another object and is represented as a +pointer in C. A child property is an embedded object and is represented by +embedding a struct member in the object structure. + +A child property has its lifecycle tied to the parent object. IOW, when an +object of MyType is destroyed, the 'out' object embedded within it will be +automatically destroyed. + +A link property will hold a reference to the object it points to, but does not +control the life cycle of the object it points to. That is, when an object of +MyType is destroyed, the object pointed to by 'in' does not necessarily get +destroyed although its reference count will be decreased. + + @@ -39,6 +42,11 @@ static void my_type_initfn(Object *obj) + MyType *my = MY_TYPE(obj); + + my->foo = 1; + + + + object_initialize(&my->out, TYPE_PIN); + + object_property_add_child(obj, "out", OBJECT(&my->out), NULL); + + + + object_property_add_link(obj, "in", TYPE_PIN, + + (Object **)&my->in, NULL); + } + + static void my_type_default_bar(MyType *obj, int foo) + +To add the properties to our object, we need to first initialize our child +object and then add the properties. This should always be done in the +constructor whenever possible.