Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Dr.Dobb's journal.2006.02

.pdf
Скачиваний:
22
Добавлен:
23.08.2013
Размер:
2.38 Mб
Скачать

As for Chain Exclusion, consider these nodes:

({1,2,3,7},{3,6},{3,4},{1,4},{5,6,7},{4,6},{2,7},{8,9}, {8,9}, n=9

In the second, third, and sixth positions in the vertices vector, you have:

{3,6},{3,4},{4,6}

Only the numbers 3,4,6 can be assigned to these vertices. From this, you infer that 3,4,6 are not a matching option in any of the remaining vertices. Thus, you can erase these numbers from all the other vertices, resulting in a new, more simple graph:

({1,2,7},{3,6},{3,4},{1},{5,7},{4,6},{2,7},{8,9}, {8,9}, n=9

You can do the same thing with {1}, so that the resulting graph is:

({2,7},{3,6},{3,4},{1},{5,7},{4,6},{2,7},{8,9}, {8,9}, n=9

Algorithms

We now present an algorithm that finds all such chains in polynomial time. The first stage is to find the best bipartite matching using the Ford Fulkerson maximum flow algorithm or any other bipartite matching algorithm (see Introduction to Algorithms, Second Edition, by Thomas H. Cormen et al., MIT Press, 2001).

Let W be the set of n cells in a row, column, or subsquare.

That means that there are n cells or vertices in W when the entire Sudoku puzzle has n n cells or vertices. Let D be the set of numbers that are matched to W. In an n n Sudoku, D is the set of numbers 1,2,3,…,n.

Let Sk be a subset of k cells out of the n cells in which the possible numbers are exactly k<n numbers Tk. We say that Sk is a “chain” if it doesn’t contain a smaller set Qr such that r<k to which only r numbers can be matched. Moreover, in our model, if the number v in D can be matched to the cell u in W, then we say that our Permutation Bipartite Graph G contains the edge =(u,v) between the vertex u from W and v from D. In addition, let M denote a maximal match between W and D. A match is a set of edges connecting cells or vertices in W to numbers or vertices in D. Each cell in W is allowed to have only one edge in M that connects it to a number in D.

Each number in D is allowed to have only one edge in M that connects it to a cell or vertex in W. Let E denote the entire set of possible vertices between W and D.

Because the vertices of Sk are only connected to vertices of Tk, the condition |M|=|W| implies that each element s Sk is matched to exactly one element t Tk and vice versa. Start scanning a vertex

u1 W. Suppose u1 Sk. We check all the vertices vi1 in D such that the edges (u1,vi1) exist in E. Because Sk is connected only to Tk,vi1 are all in Tk. We mark u1 such that we will not consider u1 again. We remove u1 from the list of vertices the algorithm will visit. Now we look for all the edges (ui1,vi1) in M that are connected to one of these vertices vi1. Obviously, ui1 are in Sk for all indices i1 because all the vertices in Tk are matched by M to vertices in Sk. We continue the process recursively. Now, for each ui1 we look for all vi2 in D that are connected to at least one of the ui1 vertices by edges. We mark ui1 such that these vertices will not be considered again. Again, the vi2 must be in Tk and we continue and look for all the edges (ui2,vi1) M. Obviously, ui2 are in Sk.

Because u1 and the vertices ui1 were removed from the list of vertices that the algorithm visits, there are fewer vertices that can be visited by the algorithm. The process is repeated until all the vertices in W it can visit are reached.

Because all the vertices it can reach are in Sk, what is left to prove is that there is no vertex u in Sk that is not visited by the algorithm. Suppose there is such a vertex u. Then, obviously, the vertex v in Tk that is matched to u could not be visited either; otherwise, u could be visited. But then, v is also not connected to any one of the vertices in Sk that were visited. So

we can define Qk–1=Sk–{u}, which is connected to |Sk|–1 vertices in D. That is a

contradiction to the requirement that Sk is minimal.

A simpler algorithm is the Pile algorithm. Let G be a Permutation Bipartite Graph. We would like to find if there exist vertices vi1,…,vik in D such that they are all connected to the same k vertices in W, uj1,…,ujk. Such vertices are called “Pile” or “Set.” The algorithm is trivial. Start traversing the vertices vk in D serially in a loop. Activate a second loop within the first loop and count all the other vertices in D that are connected to the same k vertices uj1,…,ujk in W that vk is connected to. If there are k such vertices vi1,…,vik in D, then you are done. Although this algorithm can be improved, it is efficient and its runtime is |W|2/2.

After finding a chain, the algorithm can erase all the edges that are connected to the vertices in Tk that are not connected to Sk. This operation is called “Chain Exclusion.” That is, if =(u,v) and v Tk and u Sk, then is removed from the Permutation Bipartite Graph G.

After finding a Pile, remove all the edges that connect uj1,…,ujk to D {vi1,…,vik}.

Implementation

We’ve used the Chain Exclusion and Pile Exclusion algorithms described here to

 

 

 

 

 

 

 

 

 

 

 

 

 

5

 

7

 

 

 

1

 

 

 

 

9

 

 

 

8

 

 

 

 

 

 

 

1

3

 

 

 

 

7

 

 

 

 

7

4

 

 

 

 

8

 

 

 

 

6

 

2

5

4

 

 

 

 

9

 

 

 

 

3

2

 

 

 

 

2

 

 

 

 

1

8

 

 

 

 

 

 

 

9

 

 

 

3

 

 

 

 

4

 

 

 

7

 

9

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 1: Sudoku puzzle.

build a Windows-based Sudoku solver in Visual Studio C++ 6.00. Executables and the complete source code are available electronically; see “Resource Center,” page 6. You will need to rename the file Sudoku.ex1 to Sudoku.exe. Alternatively, you can compile the entire project Sudoku.dsw or Sudoku.dsp with Visual Studio 6.00. We’ve included a simple console application, Logic.ex1, that demonstrates Chain Exclusion. This file should be renamed to Logic.exe. In Sudoku.exe, the Logic button calls the class Square3.cpp, and Square3.cpp calls Bipartite.cpp. The Logic button fills as many cells as possible with numbers, by using logic only. You can generate puzzles that usually have more than one solution by clicking on the Test button when the Unique option doesn’t have a check mark. If the Unique option is checked, the Sudoku puzzle is solved by logic only. The Very Hard option automatically checks the Unique option and takes 5 seconds. You can also enter your own puzzle and press either the Logic button or the OK button. If the puzzle can’t be solved because of contradictions, OK won’t fill it and Logic will fill as many cells as possible. When the contradiction is encountered, it inserts a double number in row, column, or square. That can be verified by the Check button.

In addition, Sudoku.exe also solves illogical puzzles via the OK button that calls recursion that in each iteration fills in numbers in locations in which there are less degrees of freedom. For more information on Sudoku puzzles, see the Daily Sudoku (http://www.dailysudoku.co.uk/) or the Times Online (http://www.timesonline

.co.uk/section/0,,18209,00.html).

DDJ

http://www.ddj.com

Dr. Dobb’s Journal, February 2006

57

G O O G L E ’ S S U M M E R O F C O D E

FreeBSD/nsswitch and Caching

Nsswitch is an extremely useful subsystem that exists on UNIX platforms such as Linux, Solaris, and FreeBSD.

It provides a flexible and convenient way to configure how name-service lookups are done. Nsswitch operates with two basic concepts — database and source. When users query the particular database (password, group, hosts, and so on) for information, nsswitch decides which source (files, NIS, LDAP) this information should be taken from. The basic idea of nsswitch is that hardcoded nameservice lookup functions (getpw**, getgr**, gethost**, getaddrinfo, and the like) are never called directly. The function:

nsdispatch(void *retval, const ns_dtab dtab[], const char *database,

const char *method_name, const ns_src defaults[], …)

is called instead. In turn, it dispatches the call to the appropriate sources.

Name: Michael A. Bushkov Contact: bushman@rsu.ru School: Rostov State University,

Russian Federation Major: Applied Mathematics Project: FreeBSD/nsswitch and

Caching

Project Page: http://wikitest.freebsd.org/moin.cgi/

NsswitchAndCachingFinalReport/ Mentors: Brooks Davis and

Jacques Vidrine

Mentoring Organization: FreeBSD (http://www.freebsd.org/)

FreeBSD nsswitch implementation supports various possible databases: password, groups, hosts, netgroups, and shells. In this project, we extended this list by adding services, rpc, protocols, OpenSSH, and Globus Grid Toolkit 4 databases. To add the support for the particular database, we had to implement several nsswitch sources for it, and then replace all hard-coded function calls with the nsdispatch calls.

To add the support for services, rpc, and protocols, we also had to change the interface of the corresponding internal reentrant libc functions to improve the compatibility with Linux/Solaris nsswitch implementations. We’ve changed the interfaces from the HP-UX style:

int getservbyname_r(const char *name, const char *proto,

struct servent *serv, struct

to Linux/Solaris style:

int getservbyname_r(const char *name, const char *proto,

struct servent *serv, char *buffer, size_t bufsize, struct servent **result);

Because all nsswitch requests are passed through nsdispatch, it’s a great place to organize caching in a general way. Caching can significantly improve system performance. It’s useful for the services database, for example, because the /etc/services file becomes bigger and bigger, and getserv** functions become slower and slower.

We had to modify the nsdispatch code, so that it could process the “cache” source. Besides, marshaling and unmarshaling routines had been implemented for every nsswitch database type.

Michael A. Bushkov

To make the cache, nsdispatch interacts with the caching daemon, which is built on top of the caching library (they were both developed during the “Summer of Code”). The caching library provides a simple interface for cache organization. It uses hash tables to store data and supports different policies, which are applied when cache size is exceeded. The caching daemon uses UNIX socket to communicate with libc to perform read/write operations.

The interesting feature of the caching daemon (and the caching library) is the multipart caching and the concept of sessions. This approach is very useful for getXXXent( ) functions. When the first call to getXXXent( ) is made, the write session is opened. If the setXXXent( ) or endXXXent( ) is called, the session is abandoned and all its data are freed. If the getXXXent( ) function indicates the successful end of a sequence, the session is gracefully closed and all session data are placed in the cache.

DDJ

Userspace Filesystems Framework for NetBSD

long time ago, the two competing

niable, microkernels have more beauty

paradigms for designing an operat-

and theoretical appeal. Since these days

Aing system were the monolithic and

everybody is using excessive hardware

microkernel approaches. While the per-

performance as an excuse to add bloat;

formance benefits of monolithic kernels

it is only fair to use it to add something

with direct access to memory are unde-

useful.

Name: Antti Kantee

Contact: pooka@cs.hut.fi

School: Helsinki University of Technology, Finland

Major: Graduate student, Computer Science

Project: Userspace Filesystems Framework for NetBSD

Project Page: http://netbsd-soc.sourceforge.net/projects/userfs/

Mentor: William Studenmund

Organization: NetBSD Project (http://www.netbsd.org/)

Implementing a filesystem in userspace is beneficial for several reasons:

Development can take advantage of a faster bugfix-compile-restart cycle. Also, debugging is easier because it is possible to run the filesystem under a normal userspace symbolic debugger.

The filesystem can access information that traditionally has been difficult to access from inside the kernel. A simple example could be a web site accessed over HTTP using a readily available HTTP library.

The actual implementation does not necessarily have to be written in C. Of course,

58

Dr. Dobb’s Journal, February 2006

http://www.ddj.com

gloox: A High-Level Jabber/XMPP Library for C++

gloox was born as part of a university project (XMPPGrid: A Grid Framework) that used Jabber/XMPP as a transport protocol. Because, at that time, there were no C++ XMPP libraries available that suited my needs, I decided to roll my own. gloox (http://camaya.net/gloox) heavily uses the Observer Pattern. There are listeners (“handlers” in gloox-speak) for almost every imaginable event that can occur, from connection establishment to error conditions. After a connection has been established, everything is event driven, and simple applications, such as bots, can easily do without a mainloop or threads. On the other hand, gloox exposes the necessary interface to manually initi-

ate fetching of data from the socket. Right after the XML parser receives a

complete stanza, it is parsed into a Stanza object that offers a convenient interface to take apart such an XML element. The respective handlers are then called based on the stanza’s type.

The library offers classes to create regular clients as well as components. These only offer basic functionality, but can be extended with several included implementations of so-called Jabber Enhancement Proposals (JEPs) to create a fullfeatured client/component.

In general, using the library is as simple as:

Creating a new Client or Component object.

Creating and registering the desired handlers.

Calling connect( ).

Most protocol enhancements follow a similar approach: They simply register as handlers for one of the Stanza types. For example, the Info/Query (IQ) mechanism of the XMPP spec is an important tool to control various aspects of a session. The basic syntax of IQ packets is always the same and different protocols are distinguished based on the payload of an IQ packet: The child element and its namespace. gloox offers a handler for these namespaces, which makes it extremely easy to implement every IQbased protocol.

Additionally, handlers for the remaining XMPP packet types (called “stanzas” in XMPP) are included, along with a generic tag handler for protocols not using these defined stanza types.

While using these interfaces, the higher level layers offer handlers themselves, with data types tailored to their needs. This minimizes the need to know the XMPP protocol by heart if the included classes are used.

Even though it is defined in the XMPP IM spec, Roster Management is an exam-

Name: Jakob Schröter Contact: js@camaya.net

School: University of Applied Sciences, Bremen, Germany Major: Computer Science

Project: gloox

Project Page: http://camaya.net/gloox/ Mentor: Peter Saint-Andre

Mentoring Organization: Jabber Software Foundation (http://www.jabber.org/)

Jakob Schröter

ple for such a higher level protocol. The RosterManager registers itself as a handler for IQ stanzas carrying “query” elements qualified by the jabber:iq:roster namespace. It can then add or remove items from a user’s contact list, and react to incoming so-called roster pushes, an updated contact list item sent by the server.

The RosterManager offers clients a rich interface to be notified about any changes happening to the contact list. Events exist for adding and removing contacts, as well as for changes in subscription states.

The decision of using/activating one (or more) of the protocol enhancements is with the user of the library. The modular structure allows addition and removal of those enhancements at runtime. More JEPs can easily be implemented, usually by creating handlers for the respective XML namespaces a JEP uses. gloox is licensed under the GPL and commercial licenses are available.

DDJ

having a userspace API for C is only half the battle (but it’s the larger half).

Leveraging existing application code written against the well-known libc filesystem API is made possible.

Producing a framework involved attaching a new filesystem to the kernel frameworks, creating a communication pipe to the userspace and a serialized representation of filesystem operations, and creating an API to which userspace implementations could attach.

Adding a new filesystem to the kernel side was mostly a question of leg work. However, one problem was having to think somewhat differently from the typical case: Usually, filesystems are implemented with a clear idea of the semantic effects of each

vnode operation. But in this case, a “generic implementation” had to be devised.

Communication to the userspace was implemented as a device node, some ioctls, and argument structures. This is an area for future work that may possibly produce a framework for generic kernel upcalls.

The userspace API is dictated by the need to have an implementation backing each vfs and vnode operation. Also, the API aims to lift the burden of communication subroutines common to all filesystem implementations without restricting the potential for, say, an asynchronous implementation.

Currently, the framework is still very much in the infant prototyping stage. After the system is stress tested, hardened, and perfected, it would be interesting to investigate providing similar frameworks in Net-

Antti Kantee

BSD for other subsystems, such as networking stacks and device drivers.

DDJ

http://www.ddj.com

Dr. Dobb’s Journal, February 2006

59

SPARQL for Sesame

The initial goal of my project was to write a Java interpreter of the SPARQL query language for use in Sesame, an RDF data server. SPARQL, the first W3C standardized query language for the RDF data format, is a step toward standardization of the Semantic Web vision of W3C. The language is reminiscent of SQL — users specify a series of set and value con-

straints on the data in the server:

SELECT ?title

WHERE { _:book :title ?title .

FILTER (?title != "Old Title") }

The server then returns data values that fit those constraints. However, RDF data

is not relational and is usually visualized as a graph of data relationships. Therefore, queries are more akin to graph pattern matching, with variables being bound to certain matched parts of the graph.

The first design was a library of classes that processed the query from within the object structure created by parsing the query into an abstract syntax tree. This design, however, suffered from one of the common problems in OO programming— a dependence on inheritance for extension. To customize the interpreter for other servers, one had to subclass certain query objects and rebuild the library.

The final design uses a combination of design patterns to overcome this dependence. The main principle of the design is the separation of interpretation logic and query data, via prolific use of the Strategy pattern. Because abstract syntax trees lend themselves ideally to the Visitor pattern, a visitor is used at interpretation time to walk the AST query structure and bind logic to each part of the query, using an Abstract Factory to create the logic ob-

jects. Developers wishing to implement a customized query interpreter can shortcut the default logic using their own factory implementation to rewrite any part of the logic, without ever needing to recompile the main library. The primary efficiency penalty of the design is found in the data interface between the library and the server. Because most servers use a slightly different data object representation, every data value that is used by the interpreter has to be passed through its own adapter, which either passes on method calls or creates a new interpreter-compatible data value. For greater speed, the most computationally intensive set logic in the interpreter can be overridden to let servers do their own native data manipulation. Hopefully, the benefits of using a standardized specification library will allow server developers to focus more on the front-end server interfaces and underlying persistent storage and less on the particular quirks of this new query language.

DDJ

Name: Ryan Levering Contact: rrlevering@yahoo.com School: Binghamton University

Major: Ph.D. candidate, Computer Science Project: SPARQL for Sesame

Project Page: http://sparql.sourceforge.net/ Mentors: Giovanni Tummarello and Jeen Broekstra

Mentoring Organization: Semedia Semantic Web and Multimedia Group (http://semedia.deit.univpm.it/).

Ryan Levering

TSC-I2: A Lightweight Implementation for Precision-Augmented Timekeeping

The quality of timekeeping is critical for many network protocols and measurement tools. TSC-I2 (TSC-Internet2) ensures accuracy and precision by making TSC rate calibration a continuous process, thus the accuracy of interpolation parameters could be ensured, which in turn results in satisfying clock precision. TSC-I2 maintains a soft clock of its own, periodically comparing this clock to the system clock. During each comparison, it synchronizes itself with the system clock, and adjusts the interpolation rate based

Xun Luo

on the offset and rate errors regarding to system clock. Whenever the accuracy of the soft clock is ensured, TSC-I2 uses this clock to report time to the library user; otherwise, the system clock value is reported. The advantage of this design is that system clock is enhanced rather than substituted.

The clock discipline algorithm is enlightened by NTP. A state-machine-controlled PLL (Phase Lock Loop) traps the rateinduced phase difference between TSC clock and system clock. Rate wander is captured within one loop delay, and corrected in three to four following loops. To

Name: Xun Luo Contact: xluo@cs.uic.edu

School: University of Illinois at Chicago Major: Ph.D. candidate, Computer Science Project: Timekeeping Using TSC Register Project Page: http://tsc-xluo.sourceforge.net/ Mentor: Jeff Boote

Organization: Internet2

(http://www.internet2.edu/)

avoid incorrect recognition of noise as a rate-induced error, two filters — a popcorn filter and a spike detector— are used. There are two usage modes: DAEMON and CLIENT. In DAEMON mode, a standalone daemon takes charge of timekeeping, serving one or more clients. In CLIENT mode, the library creates a thread running within the hosting process. Thus, it minimizes the application’s external dependency. There are also clear distinctions between TSC-I2 algorithms and its NTP counterparts, mainly due to the different natures of referencing sources. Readers interested in TSC-I2 internals can visit the project web site, where more details are illustrated.

TSC-I2, which is fully implemented in around 2000 lines of C, is fairly lightweight. It has been published under the Open Source License at http://tsc-xluo

.sourceforge.net/. TSC-I2 currently supports IA32, AMD64, and Power PC architectures, as well as Linux, FreeBSD, Mac OS X, and Microsoft Windows.

DDJ

60

Dr. Dobb’s Journal, February 2006

http://www.ddj.com

W I N D O W S / . N E T D E V E L O P E R

Viewing &

Organizing Log Files

LogChipper—a generic approach to tracking log content

PHIL GRENETZ

Developers charged with supporting mission-critical applications need to be alerted to problems when they arise in the production environment.

Once a problem is identified, it is essential that you can browse and/or search application log files for clues as to the nature and cause of the problem. Only then can the implications of the problem be assessed and resolved as quickly as possible.

Today’s applications are highly distributed. Clients interact with server processes asynchronously. As a result, logged events reflecting user activity are intermixed in the strictly chronological log files with events reflecting numerous types of notifications from server processes.

Diagnosing production problems is a form of forensic analysis. Typically, on being alerted to an application error, the first step is to open the log file in a text viewer such as Notepad and search or browse the file for the exceptional event and other events that may have contributed to it. This can be a tedious process. Relevant clues can be missed. In addition, thirdparty tools maintain log files in their own formats. A generic approach to viewing and organizing log file contents is highly valuable in such an environment. In this article, I present LogChipper — one solution to this problem for the .NET platform.

Figure 1 is LogChipper’s user interface. It uses two ListView controls to present the original view and the sorted and filtered view of events. Note the radio buttons for toggling between them. Also note the

Phil has developed software for over 20 years in the defense, publishing, database, and financial industries. He can be contacted at ivden-1@comcast.net.

checkboxes for enabling autoscroll and dynamic load and pausing the loading process. The column chooser lets users select the desired columns and rearrange them.

The Format menu is populated by the plug-in parser with items for selecting custom features offered by the plug-in. The plug-in in Figure 1 parses logs of a popular FIX engine, a protocol used in the financial industry to send buy and sell orders to the exchanges, and communicate about events on these orders (see http:// www.fixprotocol.org/).

Plug-In Architecture

LogChipper is designed as a plug-in framework. Such a framework has three key ingredients.

The interface is one ingredient. LogChipper is a WinForms application designed as several assemblies; see Figure 2. The assembly named “LogView” contains the application’s host executable. “LogViewCommon” contains definitions of some common enumeration types. “LogViewInterfaces” is the assembly that exposes the definition of the plug-in parser interface. “LogViewPlugin_FIX” contains a specific implementation of the interface for FIX engine logs. One assembly houses a definition of the plug-in interface and the other implements that interface. The plug-in interface ILogViewParser is defined in Listing One. Any parser must implement ILogViewParser. In so doing, it must be responsible for processing events into fields of information, assigning the corresponding values to grid columns, and exploiting metadata (such as data type, identifying tag, and column heading).

Dynamic activation is another key ingredient. To be effective, the application needs to be able to instantiate any parser that implements ILogViewParser at will. In the .NET Framework, the Activator class makes this possible. Listing Two demonstrates what happens when users select a plug-in parser. Note the use of the Activator class method CreateInstance. There are several overloads, but the one that takes the assembly file name, and the name of the type to be

instantiated serves the current purpose best. CreateInstance constructs an instance of the requested type and returns a System.Runtime.Remoting.ObjectHandle. Calling Unwrap on this handle reconstitutes the object. Casting this object to the requisite interface type completes the process, equivalent to calling

CoCreateInstance in COM, but without GUIDs or registry accesses.

“LogChipper is designed as a plug-in framework”

The third key ingredient of a plug-in framework is the ability to insert its custom features into the host application. Recall from Listing One that ILogViewParser defines the method GetPluginMenuItems. The plug-in implements this method by returning an array of objects of type MenuItem, defined in the System.Windows.Forms namespace. A plugin typically constructs the MenuItem array and linked MenuItem arrays (if any) representing cascading submenus during its initialization.

Continuing in Listing Two, after instantiating the plug-in parser object, the host application calls GetPluginMenuItems on the interface and populates the Format menu.

The plugin parser constructs the menus as in Listing Three(a). The menu items allow for three modes of column heading display:

Events in the FIX protocol consist of tagvalue pairs, where the tags are numeric.

The parser maintains a mapping between the numeric tags and alphabetic equivalent headings.

The modes of display provided by this parser are numeric, alphabetic equivalent, and a blend of the two.

Listing Three(b) illustrates one of the Format menu handlers.

http://www.ddj.com

Dr. Dobb’s Journal, February 2006

61

You need a way of determining which parser assemblies are available to users. You could iterate over the DLLs in the application directory using CreateInstance to determine which DLLs are .NET assemblies that implement ILogViewParser. This solution offers dynamic discovery, but can be expensive. Instead, I decided to list the available parsers in the application configuration file (Listing Four). This way, users can choose from a collection of parsers by familiar format names, while the application instantiates the parser based on the assembly DLL filename.

Multithreaded Design

To let users browse, sort, and filter logs while they are being loaded, the file I/O for loading and parsing the events is placed in its own thread. This requires some thread synchronization; for instance, to ensure that new rows are not being inserted into a grid while new sort or filter criteria are being applied.

Listing Five(a) contains a portion of the handler for opening a log file. After restoring the user’s settings for this file, it constructs a new thread to perform the file I/O and parsing. The member variable m_thPopulateList is of type Thread, defined in the System.Threading namespace.

Creating the thread is a matter of constructing a Thread object, passing a ThreadStart delegate to its constructor. Delegate is a .NET type representing a callback method with a specified signature. A ThreadStart delegate represents a callback method that takes no arguments and returns void. A ThreadStart delegate is created by passing its constructor a reference to a method that has the proper signature and is designed to perform the work of the thread. In this case, that method is

PopulateListThreadFunc.

Listing Five(b) contains a portion of

PopulateListThreadFunc demonstrating thread synchronization and indirect communication with the main thread. First,

note the use of m_SortParseMutex, a member variable of type Mutex, defined in the

System.Threading namespace.

Mutex offers a way to ensure that an operation that affects the state of a shared resource from one thread will not conflict with one in progress on another thread. A Mutex instance representing a Win32 mutex kernel object is created for each shared resource. All threads call the WaitOne method on the applicable Mutex instance before beginning an operation that affects the shared resource’s state. WaitOne blocks if another thread holds the mutex, returning only when Release has been called on it. In this case, the ListView controls must be protected from concurrent manipulation by the user and the file I/O thread. To prevent such a change from occurring while a new row is being inserted in its proper sequence into the sorted ListView control, changes to the sort sequence are synchronized via a call to WaitOne on m_SortParseMutex.

“Tailing” the File

Returning to Listing Five(b), note the references to various Boolean flags — variables m_bInitialLoadInProgress, m_bDynamicUpdate, m_bLoadPaused, and m_bStopRequested. To load new events from the log file as they are written, the I/O loop is continuous. If set to True, the variable m_bInitialLoadInProgress indicates that the end of the file has not yet been reached. Once the end of file is reached, new events (if any) are read from the file after putting the file I/O thread to sleep briefly so as not to hog the CPU when the bulk of the I/O task is finished.

Again, the UI thread communicates indirectly with the file I/O thread. The checkbox labeled “Dynamic Update” is initially checked. The variable m_bDynamicUpdate alternates between True and False as users uncheck/recheck the checkbox. While False, “tailing” the file is disabled.

Similarly, the variable m_bLoadPaused is synchronized with the state of the Pause

Load checkbox. While False, file loading is disabled. Also, when a user selects Close, Exit, or Open from the File menu and clicks OK on the confirmation prompt, the variable m_bStopRequested is set to True. On detecting that the user has confirmed closing the current file, PopulateListThreadFunc returns.

Multicolumn Sorting

The .NET Framework defines the IComparer interface in the Systems.Collections namespace. It is used to specify how pairs of objects are compared for sorting and searching purposes. For instance, IComparer is used by the static methods Sort and BinarySearch of the .NET Framework class Array.

The default sort behavior of the ListView class is case sensitive based on item text, the text displayed in the left-most column of the grid. By creating a class that implements the IComparer interface, it is possible to alter this behavior. Listing Six contains the ListViewItemComparer class, which derives from IComparer. Note that it has a custom constructor that takes an array of sort columns, a corresponding array of sort orders, and a reference to the plugin parser interface. Its implementation of the interface method Compare iterates over the sort columns starting with the most dominant sort column, using the sort order and data type of each, to determine which

ListViewItem is greater.

The data type of a field determines how to properly compare two items on that field. The parser holds the attributes of all of the fields and exposes them via the plug-in parser interface. Hence, the call to the interface method GetSortDataType is needed.

Conclusion

Among other improvements over MFC and other older frameworks, .NET represents a consistent programming model that hides the details of Win32 API programming and offers a rich class library. Although the details of filtering and parsing were beyond the scope of this article, there are many ways to present a UI for filtering the rows of a grid and to perform the filtering task. Likewise, there are numerous techniques for parsing events in a log file. Each format imposes constraints that emphasize one technique over others. The classes in the .NET namespace

System.Text.RegularExpressions unleash the power of regular expressions. They can be applied wherever a pattern can be identified in the text. It can be advantageous to have several related log files open at the same time for browsing/searching. A multidocument extension is a planned enhancement for LogChipper.

Figure 1: LogChipper’s user interface.

DDJ

62

Dr. Dobb’s Journal, February 2006

http://www.ddj.com

Listing One

/******************************************************** This file is part of the LogChipper(tm) software product.

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

********************************************************/

public interface ILogViewParser

{

void Initialize(string sFileName, Mutex sortParseMutex);

void SetListViewColumnInfo(ref ListViewColumnInfo lvColumnInfo); int FormatGridColumns(ListView listViewMain,ListView listViewSort); bool ParseLineIntoGrid(string line, ListView listViewMain,

ListView listViewSort,SortOrder order,ref int nNewRow); void GetAllHeadings(string[] sHeadings);

SortDataType GetSortDataType(int iCol); string GetColumnHeading(int iCol);

int GetColumnWidth(int iCol);

void DisplayHeading(ListView listView,int iCol,string adornedValue); void GetPluginMenuItems(MenuItem[] menuItems);

}

Listing Two

/******************************************************** This file is part of the LogChipper(tm) software product.

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

********************************************************/

System.Runtime.Remoting.ObjectHandle handle = Activator.CreateInstanceFrom(sPluginFile + ".dll", "LogViewPlugin.Parser");

m_LogViewParser = (LogViewInterfaces.ILogViewParser)handle.Unwrap(); m_LogViewParser.Initialize(sFormatFile + ".xml", m_SortParseMutex); m_nColumns = m_LogViewParser.FormatGridColumns(listViewMain, listViewSort); m_lvColumnInfo = new ListViewColumnInfo(); m_lvColumnInfo.SetSortColumns(m_anSortCols); m_lvColumnInfo.SetSortOrders(m_anSortOrders); m_LogViewParser.SetListViewColumnInfo(ref m_lvColumnInfo);

if (m_nColumns >= 1)

{

m_sFormatFileName = sFormatFile; menuFormat.MenuItems.Clear(); MenuItem[] menuItems = null;

m_LogViewParser.GetPluginMenuItems(ref menuItems);

int nMenuItems = menuItems.Length;

for (int iItem = 0; iItem < nMenuItems; iItem++)

{

menuFormat.MenuItems.Add(iItem, menuItems[iItem]);

}

}

Listing Three

(a)

/******************************************************** This file is part of the LogChipper(tm) software product.

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

********************************************************/

m_HeaderMenuItems = new MenuItem[3];

m_HeaderMenuItems[0] = new MenuItem("&Numeric Tags", new System.EventHandler( HeaderMenu_NumericTags_OnClick));

m_HeaderMenuItems[1] = new MenuItem("&Alphabetic Labels",

new System.EventHandler(HeaderMenu_AlphaLabels_OnClick)); m_HeaderMenuItems[2] = new MenuItem("&Both",

new System.EventHandler(HeaderMenu_Both_OnClick)); m_MenuItems = new MenuItem[1];

m_MenuItems[0] = new MenuItem("&Header Format", m_HeaderMenuItems);

(b)

private void

HeaderMenu_AlphaLabels_OnClick(object sender, System.EventArgs e)

{

if (m_headerFormat != HeaderFormat.Alpha)

{

m_headerFormat = HeaderFormat.Alpha;

UpdateColumnHeadings(m_asColLabels);

}

}

Listing Four

/******************************************************** This file is part of the LogChipper(tm) software product.

(continued on page 64)

http://www.ddj.com

Dr. Dobb’s Journal, February 2006

63

(continued from page 63)

 

private int m_nSortColumns;

 

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

 

private ArrayList m_anSortCols;

 

private ArrayList m_anSortOrders;

********************************************************/

 

 

private LogViewInterfaces.ILogViewParser m_LogViewParser;

 

 

<?xml version="1.0" encoding="utf-8" ?>

 

public ListViewItemComparer()

 

{

 

 

<configuration>

 

 

 

 

}

 

 

<appSettings>

 

 

 

 

public ListViewItemComparer(ArrayList anSortCol,ArrayList anSortOrder,

<add key="PluginAssembly_1" value="LogViewPlugin_FIX_4_1" />

 

 

 

 

LogViewInterfaces.ILogViewParser logViewParser)

<add key="PluginFormatFile_1" value="Fix_4_1_LogFormat" />

 

 

 

 

{

 

 

<add key="UserDataRootFolder" value="c:\Temp" />

 

 

 

 

m_nSortColumns

= anSortCol.Count;

</appSettings>

 

 

m_anSortCols

 

= anSortCol;

</configuration>

 

 

 

m_anSortOrders

= anSortOrder;

 

 

Listing Five

 

m_LogViewParser = logViewParser;

 

}

 

 

 

 

 

 

(a)

 

public int Compare(object x, object y)

 

{

 

 

 

int nRet = 0;

 

/********************************************************

 

 

 

for (int iCol = 0; iCol < m_nSortColumns; iCol++)

This file is part of the LogChipper(tm) software product.

 

{

 

 

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

 

nRet = CompareSingleColumn(x,y,(int)m_anSortCols[iCol],

********************************************************/

 

 

 

(SortOrder)m_anSortOrders[iCol]);

// Restore selected columns, their widths and order

 

if (nRet != 0)

 

{

 

 

// and selected sort columns and their sort order.

 

break;

 

RestoreViewSettings();

 

}

 

 

// Populate list view.

 

}

 

 

 

return nRet;

 

 

m_bInitialLoadInProgress = true;

 

}

 

 

m_bLoadInProgress = true;

 

public int CompareSingleColumn(object x, object y,

m_thPopulateList = new Thread(new ThreadStart(PopulateListThreadFunc));

 

 

 

int iCol, SortOrder order)

m_thPopulateList.Priority = ThreadPriority.Lowest;

 

{

 

 

m_thPopulateList.Start();

 

int nRet = 0;

 

(b)

 

string s1, s2;

 

 

SortDataType type = m_LogViewParser.GetSortDataType(iCol);

 

 

while (!m_bStopRequested)

 

switch (type)

 

 

{

 

 

{

 

 

 

 

case

SortDataType.AlphaNoCase:

int nNewRow = -1;

 

 

nRet = String.Compare(

while (((line = sr.ReadLine()) != null) &&

 

 

((ListViewItem)x).SubItems[iCol].Text,

!m_bStopRequested &&

 

 

 

((ListViewItem)y).SubItems[iCol].Text, true);

(m_bInitialLoadInProgress || m_bDynamicUpdate))

 

 

 

break;

 

{

 

 

 

case

SortDataType.AlphaCase:

if (m_bStopRequested)

 

 

nRet = String.Compare(

{

 

 

 

((ListViewItem)x).SubItems[iCol].Text,

m_bStopRequested = false;

 

 

 

 

((ListViewItem)y).SubItems[iCol].Text);

m_bLoadInProgress = false;

 

 

 

break;

 

return;

 

 

 

case

SortDataType.Date:

}

 

 

case

SortDataType.Time:

while (m_bLoadPaused)

 

 

s1 = ((ListViewItem)x).SubItems[iCol].Text;

{

 

 

s2 = ((ListViewItem)y).SubItems[iCol].Text;

Thread.Sleep(100);

 

 

if ((s1.Length == 0) || (s2.Length == 0))

if (m_bStopRequested)

 

 

{

 

 

{

 

 

 

 

 

nRet = String.Compare(s1, s2);

m_bStopRequested = false;

 

 

 

 

break;

m_bLoadInProgress = false;

 

 

 

}

 

 

return;

 

 

 

 

try

 

 

}

 

 

 

 

{

 

 

}

 

 

 

 

 

DateTime dt1 = DateTime.Parse(s1);

m_SortParseMutex.WaitOne();

 

 

 

 

DateTime dt2 = DateTime.Parse(s2);

nNewRow = -1;

 

 

 

 

nRet = DateTime.Compare(dt1, dt2);

if (m_bAutoScroll)

 

 

 

}

 

 

{

 

 

 

 

// If neither object has valid date format, compare as strings.

listViewMain.BeginUpdate();

 

 

catch

 

}

 

 

 

{

 

 

bool bParse = m_LogViewParser.ParseLineIntoGrid(line,

 

 

 

 

 

// Compare the two items as a string.

listViewMain, listViewSort, listViewSort.Sorting, ref nNewRow);

 

 

 

 

nRet = String.Compare(s1, s2);

if (m_bAutoScroll)

 

 

 

}

 

 

{

 

 

 

 

break;

 

listViewMain.EndUpdate();

 

 

 

case

SortDataType.Number:

}

 

 

double d1 = 0;

if (!bParse)

 

 

double d2 = 0;

{

 

 

s1 = ((ListViewItem)x).SubItems[iCol].Text;

m_SortParseMutex.ReleaseMutex();

 

 

s2 = ((ListViewItem)y).SubItems[iCol].Text;

break;

 

 

if ((s1 != null) && (s1.Length > 0))

}

 

 

{

 

 

if (m_bAutoScroll && (nNewRow >= 0))

 

 

 

 

 

d1 = Convert.ToSingle(s1);

 

 

 

{

 

}

 

 

 

if ((s2 != null) && (s2.Length > 0))

listViewMain.EnsureVisible(nNewRow);

 

 

{

 

 

}

 

 

 

 

 

d2 = Convert.ToSingle(s2);

m_SortParseMutex.ReleaseMutex();

 

 

 

}

 

 

}

 

 

 

 

nRet = (d1 < d2) ? -1 : 1;

}

 

 

break;

 

 

 

 

 

 

}

 

 

Listing Six

 

if(order == SortOrder.Descending)

 

{

 

 

/********************************************************

 

nRet *= -1;

 

 

}

 

 

This file is part of the LogChipper(tm) software product.

 

 

 

 

return nRet;

 

 

Copyright (C) 2004 Ivden Technologies, Inc. All rights reserved.

 

 

 

 

}

 

 

********************************************************/

 

 

 

 

 

 

 

class ListViewItemComparer : IComparer

 

}

 

 

 

 

 

DDJ

{

 

 

 

 

 

 

 

 

64

Dr. Dobb’s Journal, February 2006

http://www.ddj.com

E M B E D D E D S Y S T E M S

Porting an RTOS

To a New Hardware Platform

Porting RTXC to the NPE-C167

BYRON MILLER

Product development cycles are market driven, and market demands often require vendors to compress development schedules. One approach

to this is to simultaneously develop similar products, yet with varying levels of product complexity. However, scheduling pressures coupled with increased product complexity can be a recipe for disaster, resulting in slipped schedules and missed opportunities. Consequently, vendors are always on the alert for silver bullets, yet as developers, we know that they don’t exist. That said, it is still in our best interest to seek better ways of compressing development cycles, and one way to do this is to port existing products to new hardware platforms, adding new features along the way. This is the approach we used to demonstrate a proof-in-concept when porting a legacy security application to a new hardware platform.

Our firm was hired to make enhancements to the client’s existing 6502-based product, and we quickly realized that this platform was running out of steam. Specifically, the proposed features would significantly impact performance. Consequently, we proposed three options for fixing this problem:

Completely rewriting the application on the current hardware.

Rewriting the application on a new, higher performance hardware.

Migrating portable portions of the application to the new hardware.

Byron is an independent firmware developer specializing in microprocessor and DSP design and development for data acquisition, control, and Internet appliances. He can be reached at bmiller2@isd.net.

After considering the options, we decided to port to new hardware.

Overview

Like most firmware projects, this project consisted of software and hardware, specifically the Quadros System’s RTXC Real-Time Operating System (http://www.quadros

.com/) and test code that we ported to NorthPole Engineering’s NPE-167 Single Board Computer (http://www.npe-inc

.com/), which is based on the Infineon C167 processor.

The C167 supports a 16-channel, 10-bit A/D converter, two 16-channel capture and compare units, four-channel PWM, two general-purpose timer units, asynchronous/ synchronous serial channel (USART), highspeed synchronous serial channel, watchdog timer, bootstrap loader, 111 I/O lines, 2K internal RAM, 16 priority-level interrupt system, an eight-channel Peripheral Event Controller (PEC), and a Controller Area Network (CAN) 2.0B specification. In its basic configuration, the NPE-167 (see Figure 1) contains both 512K of Flash and SRAM. Memory is expandable to 17 MB of combined Flash and SRAM. It also supports a real-time clock, RS-232 serial port, and modified Serial Peripheral Interface (SPI) bus used to control four I/O cards.

To bring up a real-time operating system (RTOS) on a new board, most vendors provide Board Support Packages (BSPs)— software and documentation that provide guidance on getting the RTOS to execute on new hardware. RTXC’s BSP for the C167 comes with the source code for the RTOS kernel, a system-generation utility (RTXCgen) for configuring the number of tasks, timers, and resources used for the specific application. It also includes documentation, some source-code examples, and makefiles for building the kernel and a sample application.

RTXC Overview

The Real-Time eXecutive kernel (RTXC) supports three kinds of priority-based task scheduling: preemptive (the default), round-robin, and time-slice.

RTXC is robust, supports hard deadlines, changeable task priorities, time and

resource management, and intertask communication. It also has a small RAM/ROM code footprint, standard API interface, and is implemented in many processors.

RTXC is divided into nine basic components: tasks, mailboxes, messages, queues, semaphores, resources, memory partitions, timers, and Interrupt Service Routines (ISRs). These components are further subdivided into three groups that

“The Real-Time eXecutive kernel supports three kinds of priority-based task scheduling”

are used for intertask communication, synchronization, and resource management. Moreover, component functionality is accessed via the standard API interface.

Porting Activities Overview

Porting an RTOS to a new board requires four activities:

Determining the system’s architecture.

Figuring out what files to change based on the architecture.

Making changes to the files; this includes writing the code.

Creating test code and exercising the board to ensure that the RTOS is working properly.

The first activity is design related, while the others are implementation related. Moreover, the last three activities require an understanding of the new hardware — knowing the specifics of what needs to happen to make the RTOS interact with the board.

System Architecture

The purpose of determining the system architecture requirements is to identify the

http://www.ddj.com

Dr. Dobb’s Journal, February 2006

65

hardware and software components that need modifying to get the RTOS up and running on the NPE-167 board.

For most porting projects, hardware components include I/O, memory, timers, and other unique peripherals. For this project, these components are no different. We had the I/O ports that control the LEDs, CAN bus, serial communication, memory selection, and card-slot selection. Memory had both Flash and SRAM. Memory is selected through the I/O component using the SPI bus. Therefore, I/O and memory selection are interrelated. For this project, we also had to identify the timer to run RTXC’s real-time clock. The real-time clock is the master timer used for all RTOSbased time-keeping functions. Additional-

Figure 1: NPE-167 SBC.

ly, for this project, we were not going to use any other on-chip peripherals.

The best way to identify hardware components is to study the board’s schematics. Examining the NPE-167 board revealed that the I/O ports would be key for this project. Why? Because this board used the processor’s general-purpose ports to handle switches to control CAN bus operation, the board’s operating mode, control LED outputs, and memory selection. I/O cards were controlled via the SPI bus, rather than I/O ports.

Ports can be configured as either inputs or outputs. Examination of the NPE-167 board showed that 17 ports are used. Eleven ports are used as switch inputs. From the schematic we saw that switches 1–7 were used to set the MAC address for the CAN device. CAN bus speed is controlled by switches 8 – 9, while the board operating mode is controlled by switches 11–12. Switch 10 is not used. Four ports control the LEDs. There are three in total. One LED is green, one red, and the third bicolor. Thus, four outputs are required to control the three LEDs. Finally, two output ports are used as page selection for extended memory.

RTXC Task definitions - RTXC demo

 

06/14/05 23:43

Default task stack size is 512

 

 

 

 

Number of dynamic tasks is 0

 

 

 

#

Name

Pri

Entry

Stack Start Description

FPU

1

RTXCBUG

1

rtxcbug

512

-1

RTXC System Debugger N

2

SIOIDRV

2

sioidrv

128

-1

serial input drvr

N

3

SIOODRV

3

sioodrv

128

-1

serial output drvr

N

4

EXAMPLE1

9

txtask

512

22

example task 1

N

5

EXAMPLE2

8

rxtask

512

21

example task 2

N

Total number of RAM bytes required = 2480

Total number of ROM bytes required = 78

Figure 2: RTXC task definitions.

Figure 3: Test code debug session.

Referring to the schematic, we saw that the NPE board addresses up to 512K of memory before having to make use of the page-selection ports. Although we would configure the page-selection ports for the porting process, we didn’t have to use them because the total code footprint of the kernel, plus test code, is 107K. RTXC’s kernel is about 76K, and the porting test code fits within another 31K. In short, we would only use about 1/5 of the default memory to validate the porting process.

The last necessary component for the port was to determine which timer to use as the master time base. Timers are internal on the C167 processor, so they don’t show up on the schematic. So we had two options — choose a timer and write the code for that timer, or use the BSP default timer. RTXC’s C167 BSP uses a timer in its configuration. A trick to simplify the initial porting process is to use the default timer that the BSP uses. Reviewing the BSP documentation, we discovered that it uses timer 6 for the master timer. Once we determined the components associated with the porting process, we could turn our attention to figuring out which files needed to be changed.

Changing Files

We knew from the previous step that 11 ports were used for input and six ports for output. Because these were generalpurpose I/O ports, they needed to be initialized to work as either inputs or outputs. This gave us an idea of where NPE-specific initialization code needed to go — specifically, initialization code to set up these ports goes in the startup code. For this project, initialization code was located in the cstart.a66 file that is located in the Porting directory. Listing One is the code that configures the NPE-167 board I/O. Once configured, I/O can be used by higher level RTOS and API functions. Once we figured out where the I/O changes go, we needed to turn our attention to discovering and setting up the master timer.

BSP set up the master timer for us because we were using default timer 6. Setup code for this timer is located in cstart.a66 and rtxcmain.c. Listing Two is a snippet of the RTXC-specific code. After analyzing the architecture requirements, we discovered that the only file to change for porting the NPE-167 board was cstart.a66. Granted, we knew we would have to change other files as well, but those files are application specific.

Changing Files and Writing Code

This brought us to the third step, which was straightforward because we knew what needed to be changed and where. Recall that all changes for basic porting

66

Dr. Dobb’s Journal, February 2006

http://www.ddj.com