You are on page 1of 138

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project

Student Guide

Sun Microsystems, Inc. ,

Part No: 819558010 March, 2006

Copyright 2006 Sun Microsystems, Inc.

,,

All rights reserved.

Sun Microsystems, Inc. has intellectual property rights relating to technology embodied in the product that is described in this document. In particular, and without limitation, these intellectual property rights may include one or more U.S. patents or pending patent applications in the U.S. and in other countries. U.S. Government Rights Commercial software. Government users are subject to the Sun Microsystems, Inc. standard license agreement and applicable provisions of the FAR and its supplements. This distribution may include materials developed by third parties. Parts of the product may be derived from Berkeley BSD systems, licensed from the University of California. UNIX is a registered trademark in the U.S. and other countries, exclusively licensed through X/Open Company, Ltd. Sun, Sun Microsystems, the Sun logo, the Solaris logo, the Java Coffee Cup logo, docs.sun.com, Java, and Solaris are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. All SPARC trademarks are used under license and are trademarks or registered trademarks of SPARC International, Inc. in the U.S. and other countries. Products bearing SPARC trademarks are based upon an architecture developed by Sun Microsystems, Inc. The OPEN LOOK and Sun Graphical User Interface was developed by Sun Microsystems, Inc. for its users and licensees. Sun acknowledges the pioneering efforts of Xerox in researching and developing the concept of visual or graphical user interfaces for the computer industry. Sun holds a non-exclusive license from Xerox to the Xerox Graphical User Interface, which license also covers Suns licensees who implement OPEN LOOK GUIs and otherwise comply with Suns written license agreements. Products covered by and information contained in this publication are controlled by U.S. Export Control laws and may be subject to the export or import laws in other countries. Nuclear, missile, chemical or biological weapons or nuclear maritime end uses or end users, whether direct or indirect, are strictly prohibited. Export or reexport to countries subject to U.S. embargo or to entities identied on U.S. export exclusion lists, including, but not limited to, the denied persons and specially designated nationals lists is strictly prohibited. DOCUMENTATION IS PROVIDED AS IS AND ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. Copyright 2006 Sun Microsystems, Inc. ,, Tous droits rservs.

Sun Microsystems, Inc. dtient les droits de proprit intellectuelle relatifs la technologie incorpore dans le produit qui est dcrit dans ce document. En particulier, et ce sans limitation, ces droits de proprit intellectuelle peuvent inclure un ou plusieurs brevets amricains ou des applications de brevet en attente aux Etats-Unis et dans dautres pays. Cette distribution peut comprendre des composants dvelopps par des tierces personnes. Certaines composants de ce produit peuvent tre drives du logiciel Berkeley BSD, licencis par lUniversit de Californie. UNIX est une marque dpose aux Etats-Unis et dans dautres pays; elle est licencie exclusivement par X/Open Company, Ltd. Sun, Sun Microsystems, le logo Sun, le logo Solaris, le logo Java Coffee Cup, docs.sun.com, Java et Solaris sont des marques de fabrique ou des marques dposes de Sun Microsystems, Inc. aux Etats-Unis et dans dautres pays. Toutes les marques SPARC sont utilises sous licence et sont des marques de fabrique ou des marques dposes de SPARC International, Inc. aux Etats-Unis et dans dautres pays. Les produits portant les marques SPARC sont bass sur une architecture dveloppe par Sun Microsystems, Inc. Linterface dutilisation graphique OPEN LOOK et Sun a t dveloppe par Sun Microsystems, Inc. pour ses utilisateurs et licencis. Sun reconnat les efforts de pionniers de Xerox pour la recherche et le dveloppement du concept des interfaces dutilisation visuelle ou graphique pour lindustrie de linformatique. Sun dtient une licence non exclusive de Xerox sur linterface dutilisation graphique Xerox, cette licence couvrant galement les licencis de Sun qui mettent en place linterface dutilisation graphique OPEN LOOK et qui, en outre, se conforment aux licences crites de Sun. Les produits qui font lobjet de cette publication et les informations quil contient sont rgis par la legislation amricaine en matire de contrle des exportations et peuvent tre soumis au droit dautres pays dans le domaine des exportations et importations. Les utilisations nales, ou utilisateurs naux, pour des armes nuclaires, des missiles, des armes chimiques ou biologiques ou pour le nuclaire maritime, directement ou indirectement, sont strictement interdites. Les exportations ou rexportations vers des pays sous embargo des Etats-Unis, ou vers des entits gurant sur les listes dexclusion dexportation amricaines, y compris, mais de manire non exclusive, la liste de personnes qui font objet dun ordre de ne pas participer, dune faon directe ou indirecte, aux exportations des produits ou des services qui sont rgis par la legislation amricaine en matire de contrle des exportations et la liste de ressortissants spciquement designs, sont rigoureusement interdites. LA DOCUMENTATION EST FOURNIE "EN LETAT" ET TOUTES AUTRES CONDITIONS, DECLARATIONS ET GARANTIES EXPRESSES OU TACITES SONT FORMELLEMENT EXCLUES, DANS LA MESURE AUTORISEE PAR LA LOI APPLICABLE, Y COMPRIS NOTAMMENT TOUTE GARANTIE IMPLICITE RELATIVE A LA QUALITE MARCHANDE, A LAPTITUDE A UNE UTILISATION PARTICULIERE OU A LABSENCE DE CONTREFACON.

060306@2851

Contents

What is the OpenSolaris Project? .................................................................................................... 7 Web Resources for OpenSolaris ...................................................................................................... 10 Discussions .........................................................................................................................................11 Communities ......................................................................................................................................11 Projects ................................................................................................................................................11 OpenGrok .......................................................................................................................................... 12

Planning the OpenSolaris Environment ...................................................................................... 15 Development Environment Conguration ................................................................................... 17 Networking ........................................................................................................................................ 18

OpenSolaris Policies ........................................................................................................................ 21 Development Process and Coding Style ......................................................................................... 23

Features of the OpenSolaris Project ............................................................................................. 25 Overview ............................................................................................................................................ 26 Security Technology: Least Privilege ............................................................................................... 26 Predictive Self-Healing ..................................................................................................................... 26 Zones .................................................................................................................................................. 28 Branded Zones (BrandZ) ................................................................................................................. 28 Zettabyte Filesystem (ZFS) .............................................................................................................. 29 Dynamic Tracing (DTrace) .............................................................................................................. 29 Modular Debugger (MDB) .............................................................................................................. 30

Contents

Programming Concepts .................................................................................................................. 31 Process and System Management ................................................................................................... 33 Threaded Programming ................................................................................................................... 36 CPU Scheduling ................................................................................................................................ 38 Kernel Overview ................................................................................................................................ 41 Process Debugging ............................................................................................................................ 43

Getting Started With DTrace .......................................................................................................... 45 Enabling Simple DTrace Probes .............................................................................................. 47 Listing Traceable Probes ........................................................................................................... 49 Programming in D .................................................................................................................... 52

Debugging Applications With DTrace ........................................................................................... 55 Enabling User Mode Probes ............................................................................................................ 57 DTracing Applications ...................................................................................................................... 58

Debugging C++ Applications With DTrace .................................................................................. 61 Using DTrace to Prole and Debug A C++ Program .................................................................... 62

Managing Memory with DTrace and MDB ................................................................................... 71 Software Memory Management ...................................................................................................... 73 Using DTrace and MDB to Examine Virtual Memory ......................................................... 74

10

Observing Processes in Zones With DTrace ................................................................................. 85 Global and Non-Global Zones ........................................................................................................ 87 DTracing a Process Running in a Zone ................................................................................... 88

11

Conguring Filesystems With ZFS ................................................................................................. 89 Creating Pools With Mounted Filesystems .................................................................................... 91 Creating Mirrored Storage Pools ............................................................................................. 92 Creating a Filesystem and /home Directories ......................................................................... 93

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Conguring RAID-Z .................................................................................................................................94

12

Writing a Template Character Device Driver ................................................................................................95 Overview of the Template Driver Example .....................................................................................................97 Writing the Template Driver .............................................................................................................................98 Writing the Loadable Module Conguration Entry Points ..........................................................................98 Writing the Autoconguration Entry Points ................................................................................................102 Writing the User Context Entry Points ......................................................................................................... 110 Writing the Driver Data Structures ................................................................................................................ 113 Writing the Device Conguration File ..........................................................................................................120 Building and Installing the Template Driver ................................................................................................121 Testing the Template Driver ............................................................................................................................122 Adding the Template Driver ...........................................................................................................................122 Reading and Writing the Device .....................................................................................................................123 Removing the Template Driver ......................................................................................................................124 Dummy Driver Source ....................................................................................................................................125

13

Debugging Drivers With DTrace ...................................................................................................................131 Porting the smbfs Driver from Linux to the Solaris OS ...............................................................................132

1
Objectives

M O D U L E

What is the OpenSolaris Project?

The objective of this course is to learn about operating system computing by using the Solaris Operating System source code that is freely available through the OpenSolaris project. Well start by showing you where to go to access the code, communities, discussions, projects, and source browser for the OpenSolaris project. Then, well briey describe how the features and documentation enable straightforward conguration of a development environment and initiation into the development process. Finally, well work through the following labs which are designed to demonstrate typical operating system issues by using OpenSolaris:
I

Process Debugging
I I I I

Enabling Simple DTrace Probes Listing Traceable Probes Programming in D Enabling User Mode DTrace Probes

Application Debugging
I I

DTracing Applications Using DTrace to Prole and Debug a C++ Program

Memory Management
I

Using DTrace and MDB to Examine Virtual Memory

Observing Processes
I

DTracing a Process Running in a Zone

Conguring Filesystems
I I

Creating Mirrored ZFS Storage Pools Creating a Filesystem and /home Directories
7

What is the OpenSolaris Project?

I I

Conguring RAID-Z

Device Drivers
I I

Writing a Template Character Device Driver Debugging a Device Driver with DTrace

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

What is the OpenSolaris Project?

Relevance
The OpenSolaris project was launched on June 14, 2005 to create a community development effort using the Solaris OS code as a starting point. It is a nexus for a community development effort where contributors from Sun and elsewhere can collaborate on developing and improving operating system technology. The OpenSolaris source code will nd a variety of uses, including being the basis for future versions of the Solaris OS product, other operating system projects, third-party products and distributions of interest to the community. The OpenSolaris project is currently sponsored by Sun Microsystems, Inc. In the rst eight months, over 12,000 participants have become registered members. The engineering community is continually growing and changing to meet the needs of developers, system administrators, and end users of the Solaris Operating System. Teaching with the OpenSolaris project provides the following advantages over instructional operating systems:
I I

Access to code for the revolutionary technologies in the Solaris 10 operating system Access to code for a commercial OS that is used in many environments and that scales to large systems Hardware platform support including SPARC, x86 and AMD x64 architectures Leadership on 64bit computing $0.00 for innite right-to-use Free, exciting, innovative, complete, seamless, and rock-solid code base Availability under the OSI-approved Common Development and Distribution License (CDDL) allows royalty-free use, modication, and derived works

I I I I I

Module 1 What is the OpenSolaris Project?

Web Resources for OpenSolaris

Web Resources for OpenSolaris


You can download the OpenSolaris source, view the license terms and access instructions for building source and installing the pre-built archives at: http://www.opensolaris.org/os/downloads. The icons in the upper-right of the OpenSolaris web pages link you to discussions, communities, projects, downloads, and source browser resources as shown in the following graphic.

In addition, the OpenSolaris web site provides search across all of the site content and aggregated blogs, as shown in the upper-left of the graphic.

10

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Web Resources for OpenSolaris

Discussions
Discussions provide you with access to the experts who are working on new open source technologies. Discussions also provide an archive of previous conversations that you can reference for answers to your questions. See http://www.opensolaris.org/os/discussions for the complete list of forums to which you can subscribe.

Communities
Communities provide connections to other participants with similar interests in the OpenSolaris project. Communities form around interest groups, technologies, support, tools, and user groups, for example:
http://www.opensolaris.org/os/community/edu http://www.opensolaris.org/os/community/dtrace http://www.opensolaris.org/os/community/zfs http://www.opensolaris.org/os/community/zones http://www.opensolaris.org/os/community/documentation http://www.opensolaris.org/os/community/device_drivers http://www.opensolaris.org/os/community/tools http://www.opensolaris.org/os/community/os_user_groups

Academic and Research DTrace ZFS Zones Documentation Device Drivers Tools User Groups

These are only a few of over 30 communities actively working on OpenSolaris. See http://opensolaris.org/os/communities for the complete list.

Projects
Projects hosted on the opensolaris.org web site are collaborative efforts that produce objects such as code changes, documents, graphics, or joint-authored products. Projects have code repositories and committers and may live within a community or independently. New projects are initiated by participants by request on the discussions. Projects that are submitted and accepted by at least one other interested participant are given space on the projects page to get started. See http://www.opensolaris.org/os/projects for the current list of new projects.
Module 1 What is the OpenSolaris Project? 11

Web Resources for OpenSolaris

OpenGrok
OpenGrok is the fast and usable source code search and cross reference engine used in OpenSolaris. See http://cvs.opensolaris.org/source to try it out! The rst project to be hosted on opensolaris.org was OpenGrok. See http://www.opensolaris.org/os/project/opengrok to nd out about the ongoing development project. Take an online tour of the source and youll discover cleanly written, extensively commented code that reads like a book. If youre interested in working on an OpenSolaris project, you can download the complete codebase. If you just need to know how some features work in the Solaris OS, the source code browser provides a convenient alternative. OpenGrok understands various program le formats and version control histories like SCCS, RCS, and CVS, so that you can better understand the open source. The following graphic shows the results of an OpenGrok le path search on fbt.

12

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Web Resources for OpenSolaris

Module 1 What is the OpenSolaris Project?

13

14

M O D U L E

Planning the OpenSolaris Environment

Objectives
The objective of this module is to understand the system requirements, support information and documentation available for the OpenSolaris project installation and conguration.

15

Planning the OpenSolaris Environment

Additional Resources
I I

Solaris 10 Installation Guide: Basic Installations. Sun Microsystems, Inc., 2005. Sun Studio 11: C Users Guide. Sun Microsystems, Inc., 2005. Click Sun Studio 11 Collection to see Sun Studio books about dbx, dmake, Performance Analyzer, and other software development topics. Resources for Running Solaris OS on a Laptop: http://www.sun.com/bigadmin/features/articles/laptop_resources.html

16

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Development Environment Conguration

Development Environment Conguration


There is no substitute for hands-on experience with operating system code and direct access to kernel modules. The unique challenges of kernel development and access to root privileges for a system are made simpler by the tools, forums, and documentation provided for the OpenSolaris project. Consider the following features of OpenSolaris as you plan your development environment:
TABLE 21 Congurable Lab Component Support Congurable Component Support From the OpenSolaris Project

Hardware

OpenSolaris supports systems that use the SPARC and x86 families of processor architectures: UltraSPARC, SPARC64, AMD64, Pentium, and Xeon EM64T. For supported systems, see the Solaris OS Hardware Compatibility List at http://www.sun.com/bigadmin/hcl. See http://www.opensolaris.org/os/downloads for detailed instructions about how how to build from source. Pre-built OpenSolaris distributions are limited to the Solaris Express: Community Release [DVD Version], Build 32 or newer. For the OpenSolaris kernel with the GNU user environment, try http://www.gnusolaris.org/gswiki/Download-form.

Source les

Install images

BFU archives

The on-bfu-DATE.PLATFORM.tar.bz2 le is provided if you are installing from pre-built archives. The SUNWonbld-DATE.PLATFORM.tar.bz2 le is provided if you build from source.

Build tools

Module 2 Planning the OpenSolaris Environment

17

Development Environment Conguration

TABLE 21 Congurable Lab Component Support Congurable Component

(Continued)
Support From the OpenSolaris Project

Compilers and tools

Sun Studio 10 compilers and tools are freely available for use by OpenSolaris developers. See http://www.opensolaris.org/ os/community/tools/sun_studio_tools/ for instructions about how to download and install the latest versions. Also, refer to http://www.opensolaris.org/ os/community/tools/gcc for the gcc community.
I

Memory/Disk Requirements

Memory requirement: 256M minimum, 1GB recommended Disk space requirement: 350M bytes

Virtual OS environments

Zones and Branded Zones in OpenSolaris provide protected and virtualized operating system environments within an instance of Solaris, allowing one or more processes to run in isolation from other activity on the system. OpenSolaris supports Xen, an open-source virtual machine monitor developed by the Xen team at the University of Cambridge Computer Laboratory. See http://www.opensolaris.org/ os/community/xen/ for details and links to the Xen project.

OpenSolaris is also a VMWare guest, see http://opensolaris.org/os/project/content/articles/vmware for draft version of a recent article describing how to get started.

Refer to Module 2 for more information about how Zones and Branded Zones enable kernel and user mode development of Solaris and Linux applications without impacting developers in separate zones.

Networking
The OpenSolaris project meets future networking challenges by radically improving your network performance without requiring changes to your existing applications.
I

Speeds application performance by about 50 percent by using an enhanced TCP/IP stack

18

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Development Environment Conguration

Supports many of the latest networking technologies, such as 10 Gigabit Ethernet, wireless networking, and hardware ofoading Accommodates high-availability, streaming, and Voice over IP (VoIP) networking features through extended routing and protocol support Supports current IPv6 specications

Find out more about ongoing networking developments in the OpenSolaris project here: http://opensolaris.org/os/community/networking/. Participation in the OpenSolaris project can improve overall performance across your network with the latest technologies. Your lab environment becomes self-sustaining when hosted on OpenSolaris because you are always running the latest and greatest environment, empowered to update it yourself.

Module 2 Planning the OpenSolaris Environment

19

20

M O D U L E

OpenSolaris Policies

Objectives
The objective of this module is to understand at a high-level the development process steps and the coding style that is used in the OpenSolaris project.

21

OpenSolaris Policies

Additional Resources
I

OpenSolaris Development Process; http://www.opensolaris.org/os/community/onnv/os_dev_process/ C Style and Coding Standards for SunOS; http://www.opensolaris.org/os/community/documentation/getting_started_docs/

22

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Development Process and Coding Style

Development Process and Coding Style


The development process for the OpenSolaris project follows the following high-level steps: 1. Idea First, someone has an idea for an enhancement or has a gripe about a defect. Search for an existing bug or le a new bug or request for enhancement (RFE) by using the http://bugs.opensolaris.org web page. Next, announce it to other developers on the appropriate E-mail list. The announcement has the following benets:
I I I I

Precipitate discussion of the change or enhancement Determine the complexity of the proposed change(s) Gauge community interest Identify potential team members

2. Design The Design phase determines whether or not a formal design review is even needed. If a formal review is needed, complete the following next steps:
I I I I

Identify design and architectural reviewers Write a design document Write a test plan Conduct design reviews and get the appropriate approvals

3. Implementation The Implementation phase consists of the following:


I

Writing of the actual code in accordance with policies and standards

Download C Style and Coding Standards for SunOS here:http://www.opensolaris.org/os/community/documentation/getting_started_docs/


I I I I

Writing the test suites Passing various unit and pre-integration tests Writing or updating the user documentation, if needed Identifying code reviewers in preparation for integration

4. Integration Integration happens after all reviews have been completed and permission to integrate has been granted. The Integration phase is to make sure everything that was supposed to be done has in fact been done, which means conducting reviews for code, documentation, and completeness.

Module 3 OpenSolaris Policies

23

Development Process and Coding Style

The formal process document for OpenSolaris describes the previous steps in greater detail, with ow charts that illustrate the development phases. That document also details the following design principles and core values that are to be applied to source code development for the OpenSolaris project:
I

Reliability OpenSolaris must perform correctly, providing accurate results with no data loss or corruption. Availability Services must be designed to be restartable in the event of an application failure and OpenSolaris itself must be able to recover from non-fatal hardware failures. Serviceability It must be possible to diagnose both fatal and transient issues and wherever possible, automate the diagnosis. Security OpenSolaris security must be designed into the operating system, with mechanisms in place in order to audit changes done to the system and by whom. Performance The performance of OpenSolaris must be second to none when compared to other operating systems running on identical environments. Manageability It must allow for the management of individual components, software or hardware, in a consistent and straightforward manner. Compatibility New subsystems and interfaces must be extensible and versioned in order to allow for future enhancements and changes without sacricing compatibility. Maintainability OpenSolaris must be architected so that common subroutines are combined into libraries or kernel modules that can be used by an arbitrary number of consumers. Platform Neutrality OpenSolaris must continue to be platform neutral and lower level abstractions must be designed with multiple and future platforms in mind.

Refer to http://www.opensolaris.org/os/community/onnv/os_dev_process/ for more detailed information about the process that is used for collaborative development of OpenSolaris code. Like many projects, OpenSolaris enforces a coding style on contributed code, regardless of its source. This style is described in detail at http://opensolaris.org/os/community/onnv/. Two tools for checking many elements of the coding style are available as part of the OpenSolaris distribution. These tools are cstyle(1) for verifying compliance of C code with most style guidelines, and hdrchk(1) for checking the style of C and C++ headers.

24

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

M O D U L E

Features of the OpenSolaris Project

Objectives
The objective of this module is to describe the major features of the OpenSolaris project and how the features have fundamentally changed operating system computing.

25

Overview

Overview
Now that you have considered the components, processes, and guidelines for OpenSolaris development, lets briey talk about the following features of the operating environment:
I I I I I I I

Security Technology: Least Privilege Services Management Facility (SMF) Zones Branded Zones (BrandZ) Zetabyte File System (ZFS) Dynamic Tracing Facility (DTrace) Modular Debugger (MDB)

Security Technology: Least Privilege


UNIX has historically had an all-or-nothing privilege model that imposes the following restrictions:
I I I I

No way to limit root user privileges No way for non-root users to perform privileged operations Applications needing only a few privileged operations must run as root Very few are trusted with root privileges and virtually no students are so trusted

In the Solaris OS weve developed ne-grained privileges. Fine-grained privileges allows applications and users to run with just the privileges they need. The least privilege allows students to be granted the privileges that they need to complete their course work, participate in research, and maintain a portion of the campus or department infrastructure.

Predictive Self-Healing
Predictive self-healing was implemented in two ways in the Solaris 10 OS. This section describes the new Fault Management Architecture and Services Management Facility that make up the self-healing technology.

Fault Management Architecture (FMA)


The Solaris OS provides a new architecture, FMA, for building resilient error handlers, error telemetry, automated diagnosis software, response agents, and a consistent model of system failures for a management stack. Many parts of Solaris are already participating in FMA, including the CPU and Memory error handling for UltraSPARC III and IV, the UltraSPARC
26 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Overview

PCI HBAs, and more. Opteron support is scheduled for build 34. A variety of projects are underway, including full support for CPU, Memory, and I/O faults on Opteron, conversion of key device drivers, and integration with various management stacks. When a subsystem is converted to participate in Fault Management, error handling is made resilient so that the system can continue to operate despite some underlying failure, and telemetry events are produced that drive automated diagnosis and response. The Fault Management tools and architecture enable development of self-healing content for software and hardware failures, for both microscopic and macroscopic system resources, all with a unied, simple view for administrators and system management software. See http://opensolaris.org/os/community/fm for information about how to participate in the Fault Management community or to download the Fault Management MIB that is currently in development. Beyond consistent error handling,

Services Management Facility (SMF)


SMF creates a supported, unied model for management of an enormous number of services, such as email delivery, ftp requests, and remote command execution in the OpenSolaris project. The smf(5) framework replaces (in a compatible manner) the existing init.d(4) startup mechanism and includes an enhanced inetd(1M) , promoting the service to a rst-class operating system object. SMF gives developers the following:
I

Automated restart of services in dependency order due to administrative errors, software bugs, or uncorrectable hardware errors A single API for service management, conguration, and observation Access to service-based resource management Simplied boot-process debugging

I I I

See http://opensolaris.org/os/community/smf/scfdot to see a graph of the SMF services and their dependencies on an x86 system freshly installed with the Solaris OS Nevada build 24. In addition to service-level management improvements, the OpenSolaris project provides application-level features and functionality to create separate and protected run-time environments. The sophisticated resource management facilities of zones addresss the unique challenges of application development and testing in shared environments.

Module 4 Features of the OpenSolaris Project

27

Overview

Zones
A zone is a virtual operating system abstraction that provides a protected environment in which applications run. The applications are protected from each other to provide software fault isolation. To ease the labor of managing multiple applications and their environments, they co-exist within one operating system instance, and are usually managed as one entity. Zones can be combined with the resource management facilities which are present in OpenSolaris to provide more complete, isolated environments. While the zone supplies the security, name space and fault isolation, the resource management facilities can be used to prevent processes in one zone from using too much of a system resource or to guarantee them a certain service level. Together, zones and resource management are often referred to as containers. See http://opensolaris.org/os/community/zones/faq/ for answers to a large number of common questions about zones and links to the latest administration documentation. Zones provide protected environments for Solaris applications, the OpenSolaris project takes zones a step further and provides separate and protected run-time environments, for example, for Linux applications, using BrandZ.

Branded Zones (BrandZ)


BrandZ is a framework that extends the zones infrastructure to create Branded Zones, which are zones that contain non-native operating environments. A branded zone may be as simple as an environment where the standard Solaris utilities are replaced by their GNU equivalents, or as complex as a complete Linux user space. The lx brand enables Linux binary applications to run unmodied on Solaris, within zones that are running a complete Linux user space. The lx brand enables user-level Linux software to run on a machine with a OpenSolaris kernel, and includes the tools necessary to install a CentOS or Red Hat Enterprise Linux distribution inside a zone on a Solaris system. The lx brand will run on x86/x64 systems booted with either a 32-bit or 64-bit kernel. Regardless of the underlying kernel, only 32-bit Linux applications are able to run. This feature is only available for x86 and AMD x64 architectures at this time. However, porting to SPARC might be an interesting community project because BrandZ lx is still very much a work in progress. Refer to http://opensolaris.org/os/community/brandz/install/ for the installation requirements and instructions. The OpenSolaris project addresses the unique challenges of operating system development and testing for application performance using features like zones. Additionally, lesystem partitioning for kernel development is simplied by the ZFS code in the OpenSolaris project.
28 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Overview

Zettabyte Filesystem (ZFS)


ZFS lesystems are not constrained to specic devices, so they can be created easily and quickly like directories. They grow automatically within the space allocated to the storage pool. ZFS presents a pooled storage model that eliminates the concept of volumes and the associated problems of partitions, provisioning, wasted bandwidth, and stranded storage. The combined I/O bandwidth of all devices in the pool is available to all lesystems at all times. Each storage pool is comprised of one or more virtual devices, which describe the layout of physical storage and its fault characteristics. See http://www.opensolaris.org/os/community/zfs/demos/basics/ for 100 Mirrored Filesystems in 5 Minutes, a demonstration of administering mirrored pools with ZFS. In addition to pooled storage, ZFS provides RAID-Z data redundancy conguration. RAID-Z is a virtual device that stores data and parity on multiple disks, similar to RAID-5. In RAID-Z, ZFS uses variable-width RAID stripes so that all writes are full-stripe writes. This is only possible because ZFS integrates lesystem and device management in such a way that the lesystems metadata has enough information about the underlying data replication model to handle variable-width RAID stripes. RAID-Z is the worlds rst software-only solution to the RAID-5 write hole. In addition to enhanced conguration and administration features that simplify and support developer requirements, the code made available in the OpenSolaris project provides a sophisticated dynamic tracing facility (DTrace) for debugging kernel and application behavior.

Dynamic Tracing (DTrace)


DTrace provides a powerful infrastructure to permit administrators, developers, and service personnel to concisely answer arbitrary questions about the behavior of the operating system and user programs. DTrace enables you to do the following:
I I I I I I

Dynamically enable and manage thousands of probes Dynamically associate predicates and actions with probes Dynamically manage trace buffers and probe overhead Examine trace data from a live system or from a system crash dump Implement new trace data providers that plug into DTrace Implement trace data consumers that provide data display
29

Module 4 Features of the OpenSolaris Project

Overview

Implement tools that congure DTrace probes

Find the DTrace community pages here http://www.opensolaris.org/os/community/dtrace. In addition to DTrace, the OpenSolaris project provides debugging facilities for low-level types of development, for example, device driver development.

Modular Debugger (MDB)


MDB is a debugger designed to facilitate analysis of problems that require low-level debugging facilities, examination of core les, and knowledge of assembly language to diagnose and correct. Generally, kernel and device developers rely on mdb to determine why and where their code went wrong. MDB is available as two commands that share common features: mdb and kmdb. You can use the mdb command interactively or in scripts to debug live user processes, user process core les, kernel crash dumps, the live operating system, object les, and other les. You can use the kmdb command to debug the live operating system kernel and device drivers when you also need to control and halt the execution of the kernel. There is an active community for MDB, where you can ask the experts or review previous conversations and common questions. See http://www.opensolaris.org/os/community/mdb

30

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

M O D U L E

Programming Concepts

5
I I I I

Objectives
This module provides a high-level description of the fundamental concepts of the OpenSolaris programming environment, as follows: Threaded Programming Kernel Overview CPU Scheduling Process Debugging

31

Programming Concepts

Additional Resources
I

Solaris Internals (2nd Edition), Prentice Hall PTR (May 12, 2006) by Jim Mauro and Richard McDougall Solaris Systems Programming, Prentice Hall PTR (August 19, 2004), by Rich Teer Multithreaded Programming Guide. Sun Microsystems, Inc., 2005. STREAMS Programming Guide. Sun Microsystems, Inc., 2005. Solaris 64-bit Developers Guide. Sun Microsystems, Inc., 2005.

I I I I

32

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

Process and System Management


The basic unit of workload is the process. Process IDs (PIDs) are numbered sequentially throughout the system. By default, each user is assigned by the system administrator to a project, which is a network-wide administrative identier. Each successful login to a project creates a new task, which is a grouping mechanism for processes. A task contains the login process as well as subsequent child processes. The resource pools facility brings together process-bindable resources into a common abstraction called a pool. Processor sets and other entities are congured, grouped, and labelled such that workload components are associated with a subset of a systems total resources. When the pools facility is disabled, all processes belong to the same pool, pool_default, and processor sets are managed through the pset() system call. When the pools facility is enabled, processor sets must be managed by using the pools facility. New pools can be created and associated with processor sets. Processes may be bound to pools that have non-empty resource sets. If we search OpenGrok for pool.c, we nd that the code comments provide a graphical representation of these relationships:
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 * * * * * * * * * * * * * * * * * The operation that binds tasks and projects to pools is atomic. That is, either all processes in a given task or a project will be bound to a new pool, or (in case of an error) they will be all left bound to the old pool. Processes in a given task or a given project can only be bound to different pools if they were rebound individually one by one as single processes. Threads or LWPs of the same process do not have pool bindings, and are bound to the same resource sets associated with the resource pool of that process. The following picture shows one possible pool configuration with three pools and three processor sets. Note that processor set "foo" is not associated with any pools and therefore cannot have any processes bound to it. Two pools (default and foo) are associated with the same processor set (default). Also, note that processes in Task 2 are bound to different pools.

Module 5 Programming Concepts

33

Process and System Management

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Processor Sets +---------+ +--------------+========================> | default | a| | +---------+ s| | || s| | +---------+ o| | | foo | c| | +---------+ i| | || a| | +---------+ t| | +------> | bar | e| | | +---------+ d| | | | | +---------+ +---------+ +---------+ Pools | default |======| foo |======| bar | +---------+ +---------+ +---------+ @ @ @ @ @ @ b| | | | | | o| | | | | | u| +-----+ | +-------+ | +---+ n| | | | | | ....d|........|......|......|.........|.......|.... : | :: | | | :: | | : : +---+ :: +---+ +---+ +---+ :: +---+ +---+ : Processes : | p | :: | p | | p | | p | :: | p |...| p | : : +---+ :: +---+ +---+ +---+ :: +---+ +---+ : :........::......................::...............: Task 1 Task 2 Task N | | | | | | | +-----------+ | +-----------+ +--| Project 1 |--+ | Project N | +-----------+ +-----------+ This is just an illustration of relationships between processes, tasks, projects, pools, and processor sets. New types of resource sets will be added in the future.

Processes can be optionally be run inside a zone. Zones are setup by system administrators, often for security purposes, in order to isolate groups of users or processes from one another. A zone can be thought of as a container in which one or more applications run isolated from all other applications on the system.
34 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

Most software that runs on OpenSolaris will run unmodied in a zone. Since zones do not change the OpenSolaris Application Programming Interface (APIs) or Application Binary Interface (ABI), recompiling an application is not necessary in order to run it inside a zone. A small number of applications which are normally run as root or with certain privileges may not run inside a zone if they rely on being able to access or change some global resource. An example might be the ability to change the systems time-of-day clock. The few applications which fall into this category may need applications to run properly inside a zone or in some cases, should continue to be used within the global zone. Here are some guidelines:
I

An application which accesses the network and les, and performs no other I/O, should work correctly. Applications which require direct access to certain devices, for example, a disk partition, will usually work if the zone is congured correctly. However, in some cases this may increase security risks. Applications which require direct access to these devices may need to be modied to work correctly. For example, /dev/kmem, or a network device. Applications should instead use one of the many IP services.

BrandZ extends the Zones infrastructure in user space in the following ways:
I I

A brand is an attribute of a zone, set at zone conguration time. Each brand provides its own installation routine, which allows us to install an arbitrary collection of software in the branded zone. Each brand may provide pre-boot and post-boot scripts that allow us to do any nal boot-time setup or conguration. The zonecfg and zoneadm tools can set and report a zones brand type.

BrandZ provides a set of interposition points in the kernel:


I

These points are found in the syscall path, process loading path, thread creation path, etc. These interposition points are only applied to processes in a branded zone. At each of these points, a brand may choose to supplement or replace the standard behavior of the Solaris OS. Fundamentally different brands may require new interposition points.

I I

Module 5 Programming Concepts

35

Process and System Management

Threaded Programming
Now that weve learned about processes in the context of tasks, projects, resource pools, zones, and branded zones, lets discuss processes in the context of threads. Traditional UNIX already supports the concept of threads. Each process contains a single thread, so programming with multiple processes is programming with multiple threads. But, a process is also an address space, and creating a process involves creating a new address space. Communication between the threads of one process is simple because the threads share everything, inlcuding a common address space and open le descriptors. So, data produced by one thread is immediately available to all the other threads. The libraries are libpthread for POSIX threads, and libthread for OpenSolaris threads. Multithreading provides exibility by decoupling kernel-level and user-level resources. In OpenSolaris, multithreading support for both sets of interfaces is provided by the standard C library. Use pthread_create(3C) to add a new thread of control to the current process.
int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void*(*start_routine)(void *), void *arg);

The pthread_create() function is called with attr that has the necessary state behavior. start_routine is the function with which the new thread begins execution. When start_routine returns, the thread exits with the exit status set to the value returned by start_routine. pthread_create() returns zero when the call completes successfully. Any other return value indicates that an error occurred. Go to /on/usr/src/lib/libc/spec/threads.spec in OpenGrok for the complete list of pthread functions and declarations. Thread synchronization enables you to control program ow and access to shared data for concurrently executing threads. The four synchronization objects are mutex locks, read/write locks, condition variables, and semaphores.
I

Mutex locks allow only one thread at a time to execute a specic section of code, or to access specic data. Read/write locks permit concurrent reads and exclusive writes to a protected shared resource. To modify a resource, a thread must rst acquire the exclusive write lock. An exclusive write lock is not permitted until all read locks have been released. Condition variables block threads until a particular condition is true. Counting semaphores typically coordinate access to resources. The count is the limit on how many threads can have access to a semaphore. When the count is reached, the thread that is trying to access the resource blocks.

I I

36

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

Synchronization
Synchronization objects are variables in memory that you access just like data. Threads in different processes can communicate with each other through synchronization objects that are placed in threads-controlled shared memory. The threads can communicate with each other even though the threads in different processes are generally invisible to each other. Synchronization objects can also be placed in les. The synchronization objects can have lifetimes beyond the life of the creating process. Code comments in the mutex.c le reveal the following:
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ... * * * * * * * * * * * * * * * * * * * * * Implementation of all threads interfaces between ld.so.1 and libthread. In a non-threaded environment all thread interfaces are vectored to noops. When called via _ld_concurrency() from libthread these vectors are reassigned to real threads interfaces. Two models are supported: TI_VERSION == 1 Under this model libthread provides rw_rwlock/rw_unlock, through which we vector all rt_mutex_lock/rt_mutex_unlock calls. Under lib/libthread these interfaces provided _sigon/_sigoff (unlike lwp/libthread that provided signal blocking via bind_guard/bind_clear. TI_VERSION == 2 Under this model only libthreads bind_guard/bind_clear and thr_self interfaces are used. Both libthreads block signals under the bind_guard/bind_clear interfaces. Lower level locking is derived from internally bound _lwp_ interfaces. This removes recursive problems encountered when obtaining locking interfaces from libthread. The use of mutexes over reader/writer locks also enables the use of condition variables for controlling thread concurrency (allows access to objects only after their .init has completed).

Module 5 Programming Concepts

37

Process and System Management

OpenGrok results for a full search on POSIX reveal the POSIX.pod le that includes the module, as described in the following comments:
POSIX 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ... Perl interface to IEEE Std 1003.1 =head1 SYNOPSIS use POSIX; use POSIX qw(setsid); use POSIX qw(:errno_h :fcntl_h); printf "EINTR is %d\n", EINTR; $sess_id = POSIX::setsid(); $fd = POSIX::open($path, O_CREAT|O_EXCL|O_WRONLY, 0644); # note: thats a filedescriptor, *NOT* a filehandle =head1 DESCRIPTION The POSIX module permits you to access all (or nearly all) the standard POSIX 1003.1 identifiers. Many of these identifiers have been given Perl-ish interfaces. Things which are C<#defines> in C, like EINTR or O_NDELAY, are automatically exported into your namespace. All functions are only exported if you ask for them explicitly. Most likely people will prefer to use the fully-qualified function names. This document gives a condensed list of the features available in the POSIX module.

Now that you understand a bit about how synchronization objects are dened in multi-threaded programming, lets learn how these objects are managed by using scheduling classes.

CPU Scheduling
Processes run in a scheduling class with a separate scheduling policy applied to each class, as follows:
I

Realtime (RT) The highest-priority scheduling class provides a policy for those processes that require fast response and absolute user or application control of scheduling priorities. RT scheduling can be applied to a whole process or to one or more lightweight processes (LWPs) in a process. You must have the proc_priocntl privilege to use the Realtime class. See the privileges(5) man page for details.

38

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

System (SYS) The middle-priority scheduling class, the system class cannot be applied to a user process. Timeshare (TS) The lowest-priority scheduling class is TS ,which is also the default class. The TS policy distributes the processing resource fairly among processes with varying CPU consumption characteristics. Other parts of the kernel can monopolize the processor for short intervals without degrading the response time seen by the user. Inter-Active (IA) The IA policy distributes the processing resource fairly among processes with varying CPU consumption characteristics, while also providing good responsiveness for user interaction. Fair Share (FSS) The FSS policy distributes the processing resource fairly among projects, independent of the number of processes they own by specifying shares to control the process entitlement to CPU resources. Resource usage is remembered over time, so that entitlement is reduced for heavy usage and increased for light usage with respect to other projects. Fixed-Priority (FX) The FX policy provides a xed priority preemptive scheduling policy for those processes requiring that the scheduling priorities do not get dynamically adjusted by the system and that the user or application have control of the scheduling priorities. This class is a useful starting point for affecting CPU allocation policies.

A scheduling class is maintained for each lightweight process (LWP). Threads have the scheduling class and priority of their underlying LWPs. Each LWP in a process can have a unique scheduling class and priority that are visible to the kernel. Thread priorities regulate contention for synchronization objects. The RT and TS scheduling classes both call priocntl(2) to set the priority level of processes or LWPs within a process. Using OpenGrok to search the code base for priocntl, we nd the variables that are used in the RT and TS scheduling classes in the rtsched.c le as follows:
27 #pragma ident "@(#)rtsched.c 1.10 05/06/08 SMI" 28 29 #include "lint.h" 30 #include "thr_uberdata.h" 31 #include <sched.h> 32 #include <sys/priocntl.h> 33 #include <sys/rtpriocntl.h> 34 #include <sys/tspriocntl.h> 35 #include <sys/rt.h> 36 #include <sys/ts.h> 37 38 /* 39 * The following variables are used for caching information 40 * for priocntl TS and RT scheduling classs. 41 */

Module 5 Programming Concepts

39

Process and System Management

42 43 44 45 46 47 48 49 50 ...

struct pcclass ts_class, rt_class; static static static static static static static rtdpent_t *rt_dptbl; int rt_rrmin; int rt_rrmax; int rt_fifomin; int rt_fifomax; int rt_othermin; int rt_othermax; /* RT class parameter table */

Typing the man priocntl command in a terminal window shows the details of each scheduling class and describes attributes and usage. For example:
% man priocntl Reformatting page. Please Wait... done User Commands NAME priocntl - display or set scheduling parameters of specified process(es) SYNOPSIS priocntl -l priocntl -d [-i idtype] [idlist] priocntl -s [-c class] [ class-specific i idtype] [idlist] priocntl -e [-c class] [ class-specific [argument(s)] options] [priocntl(1)

options] command

DESCRIPTION The priocntl command displays or sets scheduling parameters of the specified process(es). It can also be used to display the current configuration information for the systems process scheduler or execute a command with specified scheduling parameters. Processes fall into distinct classes with a separate scheduling policy applied to each class. The process classes currently supported are the real-time class, time-sharing class, interactive class, fair-share class, and the fixed

40

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

priority class. The characteristics of these classes and the class-specific options they accept are described below in the USAGE section under the headings Real-Time Class, TimeSharing Class, Inter-Active Class, Fair-Share Class, and Fixed-Priority Class. With appropriate permissions, the --More--(4%)

Kernel Overview
Now that you have a high-level understanding of processes, threads, and scheduling, lets discuss the kernel and how kernel modules are different from user programs. The Solaris kernel does the following:
I I

Manages the system resources, including le systems, processes, and physical devices. Provides applications with system services such as I/O management, virtual memory, and scheduling. Coordinates interactions of all user processes and system resources. Assigns priorities, services resource requests, and services hardware interrupts and exceptions. Schedules and switches threads, pages memory, and swaps processes.

I I

The following section discusses several important differences between kernel modules and user programs.

Execution Differences Between Kernel Modules and User Programs


The following characteristics of kernel modules highlight important differences between the execution of kernel modules and the execution of user programs:
I

Kernel modules have separate address space. A module runs in kernel space. An application runs in user space. System software is protected from user programs. Kernel space and user space have their own memory address spaces. Kernel modules have higher execution privilege. Code that runs in kernel space has greater privilege than code that runs in user space. Kernel modules do not execute sequentially. A user program typically executes sequentially and performs a single task from beginning to end. A kernel module does not execute sequentially. A kernel module registers itself in order to serve future requests.
41

Module 5 Programming Concepts

Process and System Management

Kernel modules can be interrupted. More than one process can request your driver at the same time. For example, an interrupt handler can request your driver at the same time that your driver is serving a system call. In a symmetric multiprocessor (SMP) system, your driver could be executing concurrently on more than one CPU. Kernel modules must be preemptable. You cannot assume that your driver code is safe just because your driver code does not block. Design your driver assuming your driver might be preempted. Kernel modules can share data. Different threads of an application program need not share data. By contrast, the data structures and routines that constitute a driver are shared by all threads that use the driver. Your driver must be able to handle contention issues that result from multiple requests. Design your driver data structures carefully to keep multiple threads of execution separate.

Structural Differences Between Kernel Modules and User Programs


The following characteristics of kernel modules highlight important differences between the structure of kernel modules and the structure of user programs:
I

Kernel modules do not dene a main program. Kernel modules, including device drivers, have no main() routine. Instead, a kernel module is a collection of subroutines and data. Kernel modules are linked only to the kernel. Kernel modules do not link in the same libraries that user programs link in. The only functions a kernel module can call are functions that are exported by the kernel. Kernel modules use different header les. Kernel modules require a different set of header les than user programs require. The required header les are listed in the man page for each function. Kernel modules can include header les that are shared by user programs if the user and kernel interfaces within such shared header les are dened conditionally using the _KERNEL macro. Kernel modules should avoid global variables. Avoiding global variables in kernel modules is even more important than avoiding global variables in user programs. As much as possible, declare symbols as static. When you must use global symbols, give them a prex that is unique within the kernel. Using this prex for private symbols within the module also is a good practice. Kernel modules can be customized for hardware. Kernel modules can dedicate process registers to specic roles. Kernel code can be optimized for a specic processor. You can also have customized libraries as well, something which OpenSolaris has for some of the more recent x86/x64 and UltraSPARC platforms. So, while the kernel can dedicate certain registers to certain roles, otherwise customized code can be written for both kernel and user/libraries.

42

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Process and System Management

Kernel modules can be loaded and unloaded on demand. The collection of subroutines and data that constitute a device driver can be compiled into a single loadable module of object code. This loadable module can then be statically or dynamically linked into the kernel and unlinked from the kernel. You can add functionality to the kernel while the system is up and running. You can test new versions of your driver without rebooting your system.

Process Debugging
Debugging processes at all levels of the development stack is a key part of writing kernel modules. A full search for libthread in OpenGrok, reveals the following code comments in the mdb_tdb.c le that describe the connection between multi-threaded debugging and how mdb works:
#pragma ident "@(#)mdb_tdb.c 1.4 05/06/08 SMI" 28 29 /* 30 * libthread_db (tdb) cache 31 * 32 * In order to properly debug multi-threaded programs, the proc target must be 33 * able to query and modify information such as a threads register set using 34 * either the native LWP services provided by libproc (if the process is not 35 * linked with libthread), or using the services provided by libthread_db (if 36 * the process is linked with libthread). Additionally, a process may begin 37 * life as a single-threaded process and then later dlopen() libthread, so we 38 * must be prepared to switch modes on-the-fly. There are also two possible 39 * libthread implementations (one in /usr/lib and one in /usr/lib/lwp) so we 40 * cannot link mdb against libthread_db directly; instead, we must dlopen the 41 * appropriate libthread_db on-the-fly based on which libthread.so the victim 42 * process has open. Finally, mdb is designed so that multiple targets can be 43 * active simultaneously, so we could even have *both* libthread_dbs open at 44 * the same time. This might happen if you were looking at two multi-threaded 45 * user processes inside of a crash dump, one using /usr/lib/libthread.so and 46 * the other using /usr/lib/lwp/libthread.so. To meet these requirements, we 47 * implement a libthread_db "cache" in this file. The proc target calls 48 * mdb_tdb_load() with the pathname of a libthread_db to load, and if it is 49 * not already open, we dlopen() it, look up the symbols we need to reference, 50 * and fill in an ops vector which we return to the caller. Once an object is 51 * loaded, we dont bother unloading it unless the entire cache is explicitly 52 * flushed. This mechanism also has the nice property that we dont bother 53 * loading libthread_db until we need it, so the debugger starts up faster. 54 */

Module 5 Programming Concepts

43

Process and System Management

The following mdb commands can be used to access the LWPs of a multi-threaded program:
I I I I

$l Prints the LWP ID of the representative thread if the target is a user process. $L Prints the LWP IDs of each LWP in the target if the target is a user process. pid::attach Attaches to process by using the pid, or process ID. ::release Releases the previously attached process or core le. The process can subsequently be continued by prun(1) or it can be resumed by applying MDB or another debugger. address::context Context switch to the specied process. These commands to set conditional breakpoints are often useful. [ addr ] ::bp [+/-dDestT] [-c cmd] [-n count] sym ... Set a breakpoint at the specied locations. addr ::delete [id | all] Delete the event speciers with the given ID number.

DTrace probes are constructed in a manner similar to MDB queries. Well start the hands-on lab exercises with DTrace and then add MDB when the debugging becomes more complex.

44

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

M O D U L E

Getting Started With DTrace

Objectives
The objective of this lab is to introduce you to DTrace using a probe script for a system call using DTrace.

45

Getting Started With DTrace

Additional Resources
I

Solaris Dynamic Tracing Guide. Sun Microsystems, Inc., 2005.

46

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Getting Started With DTrace

Enabling Simple DTrace Probes


Completion of the lab exercise will result in basic understanding of DTrace probes. Were going to start learning DTrace by building some very simple requests using the probe named BEGIN, which res once each time you start a new tracing request. You can use the dtrace(1M) utilitys -n option to enable a probe using its string name.
1 2

Open a terminal window. Enable the probe:


# dtrace -n BEGIN

After a brief pause, you will see dtrace tell you that one probe was enabled and you will see a line of output indicating that the BEGIN probe red. Once you see this output, dtrace remains paused waiting for other probes to re. Since you havent enabled any other probes and BEGIN only res once, press Control-C in your shell to exit dtrace and return to your shell prompt:
3

Return to your shell prompt by pressing Control-C:


# dtrace -n BEGIN dtrace: description BEGIN matched 1 probe CPU ID FUNCTION:NAME 0 1 :BEGIN ^C #

The output tells you that the probe named BEGIN red once and both its name and integer ID, 1, are printed. Notice that by default, the integer name of the CPU on which this probe red is displayed. In this example, the CPU column indicates that the dtrace command was executing on CPU 0 when the probe red.

Module 6 Getting Started With DTrace

47

Getting Started With DTrace

You can construct DTrace requests using arbitrary numbers of probes and actions. Lets create a simple request using two probes by adding the END probe to the previous example command. The END probe res once when tracing is completed.
4

Add the END probe:


# dtrace -n BEGIN -n END dtrace: description BEGIN matched 1 probe dtrace: description END matched 1 probe CPU ID FUNCTION:NAME 0 ^C 0 2 :END #

:BEGIN

The END probe res once when tracing is completed. As you can see, pressing Control-C to exit DTrace triggers the END probe. DTrace reports this probe ring before exiting.

48

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Getting Started With DTrace

Listing Traceable Probes


The objective of this lab is to explore probes in more detail and to show you how to list the probes on a system. In the preceding examples, you learned to use two simple probes named BEGIN and END. But where did these probes come from? DTrace probes come from a set of kernel modules called providers, each of which performs a particular kind of instrumentation to create probes. For example, the syscall provider provides probes in every system call and the fbt provider provides probes into every function in the kernel. When you use DTrace, each provider is given an opportunity to publish the probes it can provide to the DTrace framework. You can then enable and bind your tracing actions to any of the probes that have been published.
1 2

Open a terminal window. Type the following command:


# dtrace

The dtrace command options are printed to the output.


3

Type the dtrace command with the -l option:


# dtrace -l | more ID PROVIDER 1 dtrace 2 dtrace 3 dtrace 4 lockstat 5 lockstat 6 lockstat 7 lockstat --More-MODULE FUNCTION NAME BEGIN END ERROR mutex_enter adaptive-acquire mutex_enter adaptive-block mutex_enter adaptive-spin mutex_exit adaptive-release

genunix genunix genunix genunix

The probes that are available on your system are listed with the following ve pieces of data:
I I

ID - Internal ID of the probe listed. Provider - Name of the Provider. Providers are used to classify the probes. This is also the method of instrumentation. Module - The name of the Unix module or application library of the probe. Function - The name of the function in which the probe exists. Name - The name of the probe.
49

I I I

Module 6 Getting Started With DTrace

Getting Started With DTrace

Pipe the previous command to wc to nd the total number of probes in your system:
# dtrace -l | wc -l 30122

The number of probes that your system is currently aware of is listed in the output. The number will vary depending on your system type.
5

Add one of the following options to lter the list:


I I I I

-P for provider -m for module -f for function -n for name

Consider the following examples:


# dtrace -l -P lockstat ID PROVIDER MODULE 4 lockstat genunix 5 lockstat genunix 6 lockstat genunix 7 lockstat genunix FUNCTION mutex_enter mutex_enter mutex_enter mutex_exit NAME adaptive-acquire adaptive-block adaptive-spin adaptive-release

Only the probes that are available in the lockstat provider are listed in the output.
# dtrace -l -m ufs ID PROVIDER 15 sysinfo 16 sysinfo 356 fbt MODULE FUNCTION NAME ufs ufs_idle_free ufsinopage ufs ufs_iget_internal ufsiget ufs allocg entry

Only the probes that are in the UFS module are listed in the output.
# dtrace -l -f open ID PROVIDER 4 syscall 5 syscall 116 fbt 117 fbt MODULE FUNCTION open open open open NAME entry return entry return

genunix genunix

Only the probes with the function name open are listed.
# dtrace -l -n start ID PROVIDER 506 proc 2766 io 2768 io 5909 io MODULE unix genunix genunix nfs FUNCTION lwp_rtt_initial default_physio aphysio nfs4_bio NAME start start start start

50

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Getting Started With DTrace

The above command lists all the probes that have the probe name start.

Module 6 Getting Started With DTrace

51

Getting Started With DTrace

Programming in D
Now that you understand a little bit about naming, enabling, and listing probes, youre ready to write the DTrace version of everyones rst program, "Hello, World." This lab demonstrates that, in addition to constructing DTrace experiments on the command line, you can also write them in text les using the D programming language.
1 2 3

Open a terminal window. In a text editor, create a new le called hello.d. Type in your rst D program:
BEGIN { trace("hello, world"); exit(0); }

4 5

Save the hello.d le. Run the program by using the dtrace -s option:
# dtrace -s hello.d dtrace: script hello.d matched 1 probe CPU ID FUNCTION:NAME 0 1 :BEGIN hello, world #

As you can see, dtrace printed the same output as before followed by the text hello, world. Unlike the previous example, you did not have to wait and press Control-C, either. These changes were the result of the actions you specied for your BEGIN probe in hello.d. Lets explore the structure of your D program in more detail in order to understand what happened. Each D program consists of a series of clauses, each clause describing one or more probes to enable, and an optional set of actions to perform when the probe res. The actions are listed as a series of statements enclosed in braces { } following the probe name. Each statement ends with a semicolon (;). Your rst statement uses the function trace() to indicate that DTrace should record the specied argument, the string hello, world, when the BEGIN probe res, and then print it out. The second statement uses the function exit() to indicate that DTrace should cease tracing and exit the dtrace command.
52 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Getting Started With DTrace

DTrace provides a set of useful functions like trace() and exit() for you to call in your D programs. To call a function, you specify its name followed by a parenthesized list of arguments. The complete set of D functions is described in Solaris Dynamic Tracing Guide. By now, if youre familiar with the C programming language, youve probably realized from the name and our examples that DTraces D programming language is very similar to C and awk(1). Indeed, D is derived from a large subset of C combined with a special set of functions and variables to help make tracing easy. If youve written a C program before, you will be able to immediately transfer most of your knowledge to building tracing programs in D. If youve never written a C program before, learning D is still very easy. But rst, lets take a step back from language rules and learn more about how DTrace works, and then well return to learning how to build more interesting D programs.

Module 6 Getting Started With DTrace

53

54

M O D U L E

Debugging Applications With DTrace

Objectives
The objective of this module is to use DTrace to monitor application events.

55

Debugging Applications With DTrace

Additional Resources
Application Packaging Developers Guide. Sun Microsystems, Inc., 2005.

56

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Enabling User Mode Probes

Enabling User Mode Probes


DTrace allows you to dynamically add probes into user level functions. The user code does not need any recompilation, special ags, or even a restart. DTrace probes can be turned on just by calling the provider. A probe description has the following syntax:
pid:mod:function:name
I I I I

pid: mod: name:

format pidprocessid (for example pid5234) name of the library or a.out(executable) name of the function entry for function entry return for function return

function:

Module 7 Debugging Applications With DTrace

57

Enabling User Mode Probes

DTracing Applications
In this exercise we will learn to use DTrace on user applications. This lab builds on the use of a process ID in the probe description to trace the associated application. The steps increase in complexity to the end of the exercise, increasing the amount and depth of information about the application behavior that is output.
1 2

From the Application or Program menu, start the calculator. Find the process ID of the process you just started
# pgrep gcalctool 8198

This number is the process ID of the calc process, we will call it procid.
3

Follow the steps below to create a D-script that counts the number of times any function in the gcalctool is called. a. In a text editor, create a new le called proc_func.d. b. Use pid$1:::entry as the probe-description. $1 is the rst argument that you will send to your script, leave the predicate part empty. c. In the action section, add an aggregate to count the number of times the function is called using the aggregate statement @[probefunc]=count().
pid$1:::entry { @[probefunc]=count(); }

d. Run the script that you just wrote.


# dtrace -qs proc_func.d procid

Replace procid with the process ID of your gcalctool e. Perform a calculation on the calculator. f. Press Control+C in the window where you ran the D-script.

58

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Enabling User Mode Probes

Note The DTrace script collects data and waits for you to stop the collection by pressing

Control+C. If you do not need to print the aggregation you collected, DTrace will print it for you.

Now, modify the script to only count functions from the libc library. a. Copy the proc_func.d to proc_libc.d. b. Modify the probe description in the proc_libc.d le to the following:
pid$1:libc::entry

c. Your new script should look like the following:


pid$1:libc::entry { } 5 @[probefunc]=count();

Now run the script.


# dtrace -qs proc_libc.d procid

Replace procid with the process ID of your gcalctool a. Perform a calculation on the calculator. b. Press Control+C in the window where you ran the D-script to see the output.
6

Finally, modify the script to nd how much time is spent in each function. a. Create a le and name it func_time.d. We will use two probe descriptions in func_time.d. b. Write the rst probe as follows:
pid$1:::entry

c. Write the second probe as follows:


pid$1:::return

d. In the action section of the rst probe, save timestamp in variable ts. Timestamp is a DTrace built-in that counts the number of nanoseconds from a point in the past.
Module 7 Debugging Applications With DTrace 59

Enabling User Mode Probes

e. In the action section of the second probe calculate nanoseconds that have passed using the following aggregation:
@[probefunc]=sum(timestamp - ts)

f. The new func_time.d script should match the following:


pid$1:::entry { } { } 7 ts = timestamp; pid$1:::return /ts/ @[probefunc]=sum(timestamp - ts);

Run the new func_time.d script:


# dtrace -qs func_time.d procid

Replace procid with the process ID of your gcalctool a. Perform a calculation on the calculator. b. Press Control+C in the window where you ran the D-script to see the output.
^C gdk_xid__equal _XSetLastRequestRead _XDeq ... 2468 2998 3092

The left column shows you the name of the function and the right column shows you the amount of wall clock time that was spent in that function. The time is in nanoseconds.

60

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

M O D U L E

Debugging C++ Applications With DTrace

Objectives
The examples in this module demonstrate the use of DTrace to diagnose C++ application errors. These examples are also used to compare DTrace with other application debugging tools, including Sun Studio 10 software and mdb.

61

Using DTrace to Prole and Debug A C++ Program

Using DTrace to Prole and Debug A C++ Program


A sample program CCtest was created to demonstrate an error common to C++ applications -- the memory leak. In many cases, a memory leak occurs when an object is created, but never destroyed, and such is the case with the program contained in this module. When debugging a C++ program, you may notice that your compiler converts some C++ names into mangled, semi-intelligible strings of characters and digits. This name mangling is an implementation detail required for support of C++ function overloading, to provide valid external names for C++ function names that include special characters, and to distinguish instances of the same name declared in different namespaces and classes. For example, using nm to extract the symbol table from a sample program named CCtest produces the following output:
# /usr/ccs/bin/nm ... [61] | 134549248| [85] | 134549301| [76] | 134549136| [62] | 134549173| [64] | 134549136| [89] | 134549173| [80] | 134616000| [91] | 134549348| ... CCtest 53|FUNC 47|FUNC 37|FUNC 71|FUNC 37|FUNC 71|FUNC 16|OBJT 16|FUNC |GLOB |GLOB |GLOB |GLOB |GLOB |GLOB |GLOB |GLOB |0 |0 |0 |0 |0 |0 |0 |0 |9 |9 |9 |9 |9 |9 |18 |9 |__1cJTestClass2T5B6M_v_ |__1cJTestClass2T6M_v_ |__1cJTestClass2t5B6M_v_ |__1cJTestClass2t5B6Mpc_v_ |__1cJTestClass2t6M_v_ |__1cJTestClass2t6Mpc_v_ |__1cJTestClassG__vtbl_ |__1cJTestClassJClassName6kM_pc_

Note Source code and makele for CCtest are included at the end of this module.

From this output, you may correctly assume that a number of these mangled symbols are associated with a class named TestClass, but you cannot readily determine whether these symbols are associated with constructors, destructors, or class functions. The Sun Studio compiler includes the following three utilities that can be used to translate the mangled symbols to their C++ counterparts: nm -C, dem, and c++filt.
Note Sun Studio 10 software is used here, but the examples were tested with both Sun Studio 9

and 10. If your C++ application was compiled with gcc/g++, you have an additional choice for demangling your application -- in addition to c++filt, which recognizes both Sun Studio and
62 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Using DTrace to Prole and Debug A C++ Program

GNU mangled names, the open source gc++filt found in /usr/sfw/bin can be used to demangle the symbols contained in your g++ application. Examples: Sun Studio symbols without c++filt:
# nm [65] [56] [92] ... CCtest | grep TestClass | 134549280| 37|FUNC |GLOB |0 |9 |__1cJTestClass2t6M_v_ | 134549352| 54|FUNC |GLOB |0 |9 |__1cJTestClass2t6Mi_v_ | 134549317| 35|FUNC |GLOB |0 |9 |__1cJTestClass2t6Mpc_v_

Sun Studio symbols with c++filt:


# nm [65] [56] [92] ... CCtest | grep TestClass | c++filt | 134549280| 37|FUNC |GLOB |0 |9 |TestClass::TestClass() | 134549352| 54|FUNC |GLOB |0 |9 |TestClass::TestClass(int) | 134549317| 35|FUNC |GLOB |0 |9 |TestClass::TestClass(char*)

g++ symbols without gc++filt:


[86] | 134550070| 41|FUNC |GLOB |0 |12 |_ZN9TestClassC1EPc [110] | 134550180| 68|FUNC |GLOB |0 |12 |_ZN9TestClassC1Ei [114] | 134549984| 43|FUNC |GLOB |0 |12 |_ZN9TestClassC1Ev ...

g++ symbols with gc++filt:


# nm gCCtest | grep TestClass | gc++filt [86] | 134550070| 41|FUNC |GLOB |0 |12 |TestClass::TestClass(char*) [110] | 134550180| 68|FUNC |GLOB |0 |12 |TestClass::TestClass(int) [114] | 134549984| 43|FUNC |GLOB |0 |12 |TestClass::TestClass() ...

And nally, displaying symbols with nm -C:


[64] | 134549344| 71|FUNC |GLOB |0 |9 |TestClass::TestClass() [__1cJTestClass2t6M_v_] [87] | 134549424| 70|FUNC |GLOB |0 |9 |TestClass::TestClass(const char*) [__1cJTestClass2t6Mpkc_v_] [57] | 134549504| 95|FUNC |GLOB |0 |9 |TestClass::TestClass(int) [__1cJTestClass2t6Mi_v_]

Lets use this information to create a DTrace script to perform an aggregation on the object calls associated with our test program. We can use the DTrace pid provider to enable probes associated with our mangled C++ symbols. To test our constructor/destructor theory, lets start by counting the following:
Module 8 Debugging C++ Applications With DTrace 63

Using DTrace to Prole and Debug A C++ Program

I I

The number of objects created -- calls to new() The number of objects destroyed -- calls to delete()

Use the following script to extract the symbols corresponding to the new() and delete() functions from the CCtest program:
# dem nm CCtest | awk -F\| { print $NF; } | egrep "new|delete" __1c2k6Fpv_v_ == void operator delete(void*) __1c2n6FI_pv_ == void*operator new(unsigned)

The corresponding DTrace script is used to enable probes on new() and delete() (saved as CCagg.d):
#!/usr/sbin/dtrace -s pid$1::__1c2n6FI_pv_: { @n[probefunc] = count(); } pid$1::__1c2k6Fpv_v_: { @d[probefunc] = count(); } END { printa(@n); printa(@d); }

Start the CCtest program in one window, then execute the script we just created in another window as follows:
# dtrace -s ./CCagg.d pgrep CCtest | c++filt

The DTrace output is piped through c++filt to demangle the C++ symbols, with the following caution.
Caution You cant exit the DTrace script with a ^C as you would do normally because c++filt

will be killed along with DTrace and youre left with no output. To display the output of this command, go to another window on your system and type:
# pkill dtrace

Use this sequence of steps for the rest of the exercises:


64 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Using DTrace to Prole and Debug A C++ Program

Window 1:
# ./CCtest

Window 2:
# dtrace -s scriptname | c++filt

Window 3:
# pkill dtrace

The output of our aggregation script in window 2 should look like this:
void*operator new(unsigned) void operator delete(void*) 12 8

So, we may be on the right track with the theory that we are creating more objects than we are deleting. Lets check the memory addresses of our objects and attempt to match the instances of new() and delete(). The DTrace argument variables are used to display the addresses associated with our objects. Since a pointer to the object is contained in the return value of new(), we should see the same pointer value as arg0 in the call to delete(). With a slight modication to our initial script, we now have the following script, named CCaddr.d:
#!/usr/sbin/dtrace -s #pragma D option quiet /* __1c2k6Fpv_v_ == void operator delete(void*) __1c2n6FI_pv_ == void*operator new(unsigned) */ /* return from new() */ pid$1::__1c2n6FI_pv_:return { printf("%s: %x\n", probefunc, arg1); } /* call to delete() */ pid$1::__1c2k6Fpv_v_:entry { printf("%s: %x\n", probefunc, arg0); }

Execute this script:


# dtrace -s ./CCaddr.d pgrep CCtest | c++filt

Module 8 Debugging C++ Applications With DTrace

65

Using DTrace to Prole and Debug A C++ Program

Wait for a bit, then type this in window 3:


# pkill dtrace

Our output looks like a repeating pattern of three calls to new() and two calls to delete():
void*operator void*operator void*operator void operator void operator new(unsigned): new(unsigned): new(unsigned): delete(void*): delete(void*): 809e480 8068a70 809e4a0 8068a70 809e4a0

As you inspect the repeating output, a pattern emerges. It seems that the rst new() of the repeating pattern does not have a corresponding call to delete(). At this point we have identied the source of the memory leak! Lets continue with DTrace and see what else we can learn from this information. We still do not know what type of class is associated with the object created at address 809e480. Including a call to ustack() on entry to new() provides a hint. Heres the modication to our previous script, renamed CCstack.d:
#!/usr/sbin/dtrace -s #pragma D option quiet /* __1c2k6Fpv_v_ == void operator delete(void*) __1c2n6FI_pv_ == void*operator new(unsigned) */ pid$1::__1c2n6FI_pv_:entry { ustack(); } pid$1::__1c2n6FI_pv_:return { printf("%s: %x\n", probefunc, arg1); } pid$1::__1c2k6Fpv_v_:entry { printf("%s: %x\n", probefunc, arg0); }

Execute CCstack.d in Window 2, then type pkill dtrace in Window 3 to print the following output:
66 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Using DTrace to Prole and Debug A C++ Program

# dtrace -s ./CCstack.d pgrep CCtest | c++filt

libCrun.so.1void*operator new(unsigned) CCtestmain+0x19 CCtest0x8050cda void*operator new(unsigned): 80a2bd0 libCrun.so.1void*operator new(unsigned) CCtestmain+0x57 CCtest0x8050cda void*operator new(unsigned): 8068a70 libCrun.so.1void*operator new(unsigned) CCtestmain+0x9a CCtest0x8050cda void*operator new(unsigned): 80a2bf0 void operator delete(void*): 8068a70 void operator delete(void*): 80a2bf0

The ustack() data tells us that new() is called from main+0x19, main+0x57, and main+0x9a -were interested in the object associated with the rst call to new(), at main+0x19. To determine the type of constructor called at main+0x19, we can use mdb as follows:
# gcore pgrep CCtest gcore: core.1478 dumped # mdb core.1478 Loading modules: [ libc.so.1 ld.so.1 ] > main::dis main: pushl %ebp main+1: movl %esp,%ebp main+3: subl $0x38,%esp main+6: movl %esp,-0x2c(%ebp) main+9: movl %ebx,-0x30(%ebp) main+0xc: movl %esi,-0x34(%ebp) main+0xf: movl %edi,-0x38(%ebp) main+0x12: pushl $0x8 main+0x14: call -0x2e4 <PLT=libCrun.so.1__1c2n6FI_pv_> main+0x19: addl $0x4,%esp main+0x1c: movl %eax,-0x10(%ebp) main+0x1f: movl -0x10(%ebp),%eax main+0x22: pushl %eax main+0x23: call +0x1d5 <__1cJTestClass2t5B6M_v_> ...

Module 8 Debugging C++ Applications With DTrace

67

Using DTrace to Prole and Debug A C++ Program

Our constructor is called after the call to new, at offset main+0x23. So, we have identied a call to the constructor __1cJTestClass2t5B6M_v_ that is never destroyed. Using dem to demangle this symbol produces:
# dem __1cJTestClass2t5B6M_v_ __1cJTestClass2t5B6M_v_ == TestClass::TestClass #Nvariant 1()

Thus, a call to new TestClass() at main+0x19 is the cause of the memory leak. Examining the CCtest.cc source le reveals:
... t = new TestClass(); cout << t->ClassName(); t = new TestClass((const char *)"Hello."); cout << t->ClassName(); tt = new TestClass((const char *)"Goodbye."); cout << tt->ClassName(); delete(t); delete(tt); ...

Its clear that the rst use of the variable t = new TestClass(); is overwritten by the second use: t = new TestClass((const char *)"Hello.");. The memory leak has been identied and a x can be implemented. The DTrace pid provider allows you to enable a probe at any instruction associated with a process that is being examined. This example is intended to model the DTrace approach to interactive process debugging. DTrace features used in this example include: aggregations, displaying function arguments and return values, and viewing the user call stack. The dem and c++filt commands in Sun Studio software and the gc++filt in gcc were used to extract the function probes from the program symbol table and display the DTrace output in a source-compatible format. Source les created for this example:
EXAMPLE 81 TestClass.h

class TestClass { public: TestClass(); TestClass(const char *name); TestClass(int i); virtual ~TestClass(); virtual char *ClassName() const;

68

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Using DTrace to Prole and Debug A C++ Program

EXAMPLE 81 TestClass.h

(Continued)

private: char *str; }; TestClass.cc: #include #include #include #include #include <stdio.h> <string.h> <stdlib.h> <unistd.h> "TestClass.h"

TestClass::TestClass() { str=strdup("empty."); } TestClass::TestClass(const char *name) { str=strdup(name); } TestClass::TestClass(int i) { str=(char *)malloc(128); sprintf(str, "Integer = %d", i); } TestClass::~TestClass() { if ( str ) free(str); } char *TestClass::ClassName() const { return str; }
EXAMPLE 82 CCtest.cc

#include #include #include #include #include

<iostream.h> <stdio.h> <stdlib.h> <unistd.h> "TestClass.h"

int main(int argc, char **argv)

Module 8 Debugging C++ Applications With DTrace

69

Using DTrace to Prole and Debug A C++ Program

EXAMPLE 82 CCtest.cc

(Continued)

{ TestClass *t; TestClass *tt; while (1) { t = new TestClass(); cout << t->ClassName(); t = new TestClass((const char *)"Hello."); cout << t->ClassName(); tt = new TestClass((const char *)"Goodbye."); cout << tt->ClassName(); delete(t); delete(tt); sleep(1); } }
EXAMPLE 83 Makele

OBJS=CCtest.o TestClass.o PROGS=CCtest CC=CC all: $(PROGS) echo "Done." clean: rm $(OBJS) $(PROGS) CCtest: $(OBJS) $(CC) -o CCtest $(OBJS) .cc.o: $(CC) $(CFLAGS) -c $<

70

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

M O D U L E

Managing Memory with DTrace and MDB

Objectives
This module will build on what weve learned about using DTrace to observe processes by examining a page fault. Then, well incorporate low-level debugging with MDB to nd the problem in the code.

71

Managing Memory with DTrace and MDB

Additional Resources
Solaris Modular Debugger Guide. Sun Microsystems, Inc., 2005.

72

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

Software Memory Management


OpenSolaris memory management uses software constructs called segments to manage virtual memory of processes as well as the kernel itself. Most of the data structures involved in the software side of memory management are dened in /usr/include/vm/*.h. In this module, well examine the code and data structures used to handle page faults.

Module 9 Managing Memory with DTrace and MDB

73

Software Memory Management

Using DTrace and MDB to Examine Virtual Memory


The objective of this lab is to examine a page fault using DTrace and MDB. Well start with a DTrace script to trace the actions of a single page fault for a given process. The script prints the user virtual address that caused the fault, and then traces every function that is called from the time of the fault until the page fault handler returns. Well use the output of the script to determine what source code needs to be examined for more detail.
Note In this module, weve added text to the extensive code output to guide the exercise. Look

for the <----symbol to nd associated text in the output.

1 2

Open a terminal window. Create a le called pagefault.d with the following script:
#!/usr/sbin/dtrace -s #pragma D option flowindent pagefault:entry /execname == $$1/ { printf("fault occurred on address = %p\n", args[0]); self->in = 1; } pagefault:return /self->in == 1/ { self->in = 0; exit(0); } entry /self->in == 1/ { } return /self->in == 1/ { }

74

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

Run the script on Mozilla.


Note You need to specify mozilla-bin as the executable name, as mozilla is not an exact

match with the name. Also, assertions are turned on, so youll see various calls to mutex_owner(), for instance, which is only used with ASSERT(). Assertions are turned on only for debug kernels.
# ./pagefault.d mozilla-bin dtrace: script ./pagefault.d matched 42626 probes CPU FUNCTION 0 -> pagefault fault occurred on address = fb985ea2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | pagefault:entry <-- i86pc/vm/vm_machdep.c or sun4/vm/vm_dep.c -> as_fault <-- generic address space fault common/vm/vm_as.c -> as_segat -> avl_find <-- segments are in AVL tree -> as_segcompar <-- search segments for segment <- as_segcompar <-- containing fault address -> as_segcompar <-- common/vm/vm_as.c <- as_segcompar -> as_segcompar <- as_segcompar -> as_segcompar <- as_segcompar -> as_segcompar <- as_segcompar -> as_segcompar <- as_segcompar -> as_segcompar <- as_segcompar -> as_segcompar <- as_segcompar <- avl_find <- as_segat -> segvn_fault <-- segment containing fault is found, (not SEGV) <-- common/vm/seg_vn.c -> hat_probe <-- look for page table entry for page <-- i86pc/vm/hat_i86.c or sfmmu/vm/hat_sfmmu.c -> htable_getpage <-- page tables are hashed on x86 -> htable_getpte <-- i86pc/vm/htable.c -> htable_lookup <- htable_lookup -> htable_va2entry <- htable_va2entry

Module 9 Managing Memory with DTrace and MDB

75

Software Memory Management

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0

-> x86pte_get <-- return a page table entry -> x86pte_access_pagetable -> hat_kpm_pfn2va <- hat_kpm_pfn2va <- x86pte_access_pagetable -> x86pte_release_pagetable <- x86pte_release_pagetable <- x86pte_get <- htable_getpte <- htable_getpage -> htable_release <- htable_release <- hat_probe -> fop_getpage <-- file operation to retrieve page(s) -> ufs_getpage <-- file is in ufs fs (common/fs/ufs/ufs_vnops.c) -> bmap_has_holes <-- check for sparse file <- bmap_has_holes -> page_lookup <-- check for page already in memory -> page_lookup_create <-- common/vm/vm_page.c <- page_lookup_create <-- create page if needed <- page_lookup -> ufs_getpage_miss <-- page wasnt in memory -> bmap_read <-- get block number of page from inode -> bread_common -> getblk_common <- getblk_common <- bread_common <- bmap_read -> pvn_read_kluster &lt-- read some pages (common/vm/vm_pvn.c) -> page_create_va &lt-- create some pages <- page_create_va -> segvn_kluster <- segvn_kluster <- pvn_read_kluster -> pageio_setup <-- setup page(s) for io common/os/bio.c <- pageio_setup -> lufs_read_strategy <-- logged ufs read -> bdev_strategy <-- read block device (disk) common/os/driver.c -> cmdkstrategy <-- common disk driver (cmdk(7D)) <-- common/io/dktp/disk/cmdk.c -> dadk_strategy <-- direct attached disk (dad(7D)) <-- used for ide disks (common/io/dktp/dcdev/dadk.c) <-- driver sets up dma and starts page in <- dadk_strategy <- cmdkstrategy

76

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

<- bdev_strategy -> biowait <-- wait for pagein to complete common/os/bio.c -> sema_p <-- wakeup via sema_v from completion interrupt -> swtch <-- let someone else run (common/disp/disp.c) -> disp <-- dispatch to next thread to run <- disp -> resume <-- actual switching occurs here <-- intel/ia32/ml/swtch.s or sun4/ml/swtch.s -> savectx <-- save old context <- savectx <-- someone else is running here... -> restorectx <-- restore context (weve been awakened) <- restorectx <- resume <- swtch <- sema_p <- biowait -> pageio_done <-- undo pageio_setup <- pageio_done -> pvn_plist_init <- pvn_plist_init <- ufs_getpage_miss <-- page is in memory <- ufs_getpage <- fop_getpage -> segvn_faultpage <-- call hat to load pte(s) for page(s) -> hat_memload -> page_pptonum <-- get page frame number <- page_pptonum -> hati_mkpte <-- build page table entry <- hati_mkpte -> hati_pte_map <-- locate entry in page table -> x86_hm_enter <- x86_hm_enter -> hment_prepare <- hment_prepare -> x86pte_set <-- fill in pte into page table -> x86pte_access_pagetable -> hat_kpm_pfn2va <- hat_kpm_pfn2va <- x86pte_access_pagetable -> x86pte_release_pagetable <- x86pte_release_pagetable <- x86pte_set -> hment_assign <- hment_assign

Module 9 Managing Memory with DTrace and MDB

77

Software Memory Management

0 0 0 0 0 0 0 0 #

-> x86_hm_exit <- x86_hm_exit <- hati_pte_map <- hat_memload <- segvn_faultpage <- segvn_fault <- as_fault <- pagefault

Remember that the above output has been shortened. At a high level, the following has happened on the page fault:
I I I

The pagefault() routine is called to handle page faults. The pagefault() routine calls as_fault() to handle faults on a given address space. as_fault() walks an AVL tree of seg structures looking for a segment containing the faulting address. If no such segment is found, the process is sent a SIGSEGV (segmentation violation) signal. If the segment is found, a segment specic fault handler is called. For most segments, this is segvn_fault() segvn_fault() looks for the faulting page already in memory. If the page already exists (but has been freed), it is "reclaimed" off the free list. If the page does not already exist, we need to page it in. Here, the page is not already in memory, so we call ufs_getpage(). ufs_getpage() nds the block number(s) of the page(s) within the le system by calling bmap_read(). Then we call a device driver strategy routine, see strategy(9E) for an overview of what the strategy routine is supposed to do. While the page is being read, the thread causing the page fault blocks (i.e., switches out) via a call to swtch(). At this point, other threads will run. When the paging I/O has completed, the disk driver interrupt handler wakes up the blocked mozilla-bin thread. The disk driver returns through the le system code out to segvn_fault(). segvn_fault() then calls segvn_faultpage(). segvn_faultpage() calls the HAT (Hardware Address Translation) layer to load the page table entry(s) (PTE)s for the page. At this point, the virtual address that caused the page fault should now be mapped to a valid physical page. When pagefault() returns, the instruction causing the page fault will be retried and should now complete successfully.

I I I

78

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

Use mdb to examine the kernel data structures and locate the page of physical memory that corresponds to the fault as follows: a. Open a terminal window. b. Find the number of segments used by mozilla by using pmap as follows:
# pmap -x pgrep mozilla-bin | wc 368 2730 23105 #

The output shows that there are approximately 368 segments.


Note The search for the segment containing the fault address found the correct segment

after 8 segments. See calls to as_segcompar in the DTrace output above. Using an AVL tree shortens the search! c. Use mdb to locate the segment containing the fault address.
Note If you want to follow along, you may want to use: ::log /tmp/logfile in mdb and

then !vi /tmp/logfile to search. Or, you can just run mdb within an editor buffer.
# mdb -k Loading modules: [ unix krtld genunix specfs dtrace ufs ip sctp usba random fctl s1394 nca lofs crypto nfs audiosup sppp cpc fcip ptm ipc ] > ::ps !grep mozilla-bin <-- find the mozilla-bin process R 933 919 887 885 100 0x42014000 ffffffff81d6a040 mozilla-bin > ffffffff81d6a040::print proc_t p_as | ::walk seg | ::print struct seg <-- Lots of output has been omitted... --> { s_base = 0xfb800000 <-- this is the seg we want, fault addr (fb985ea2) s_size = 0x561000 <-- greater/equal to base and < base+size s_szc = 0 s_flags = 0 s_as = 0xffffffff828b61d0 s_tree = { avl_child = [ 0xffffffff82fa7920, 0xffffffff82fa7c80 ] avl_pcb = 0xffffffff82fa796d } s_ops = segvn_ops s_data = 0xffffffff82d85070 }

Module 9 Managing Memory with DTrace and MDB

79

Software Memory Management

<-- and lots more output omitted --> > ffffffff82d85070::print segvn_data_t <-- from s_data { lock = { _opaque = [ 0 ] } segp_slock = { _opaque = [ 0 ] } pageprot = 0x1 prot = 0xd maxprot = 0xf type = 0x2 offset = 0 vp = 0xffffffff82f9e480 <-- points to a vnode_t anon_index = 0 amp = 0 <-- well look at anonymous space later vpage = 0xffffffff82552000 cred = 0xffffffff81f95018 swresv = 0 advice = 0 pageadvice = 0x1 flags = 0x490 softlockcnt = 0 policy_info = { mem_policy = 0x1 mem_reserved = 0 } } > ffffffff82f9e480::print vnode_t v_path v_path = 0xffffffff82f71090 "/usr/sfw/lib/mozilla/components/libgklayout.so" > fb985ea2-fb800000=K <-- offset within segment 185ea2 <-- rounding down to page boundary gives 185000 (4kpage size) > ffffffff82f9e480::walk page !wc <-- walk list of pages on vnode_t 1236 1236 21012 <-- 1236 pages, (not all are necessarily valid) > ffffffff82f9e480::walk page | ::print page_t <-- walk page list on vnode <-- lots of pages omitted in output --> { p_offset = 0x185000 <-- here is matching page p_vnode = 0xffffffff82f9e480

80

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

p_selock = 0 p_selockpad = 0 p_hash = 0xfffffffffae21c00 p_vpnext = 0xfffffffffaca9760 p_vpprev = 0xfffffffffb3467f8 p_next = 0xfffffffffad8f800 p_prev = 0xfffffffffad8f800 p_lckcnt = 0 p_cowcnt = 0 p_cv = { _opaque = 0 } p_io_cv = { _opaque = 0 } p_iolock_state = 0 p_szc = 0 p_fsdata = 0 p_state = 0 p_nrm = 0x2 p_embed = 0x1 p_index = 0 p_toxic = 0 p_mapping = 0xffffffff82d265f0 p_pagenum = 0xbd62 <-- the page frame number of page p_share = 0 p_sharepad = 0 p_msresv_1 = 0 p_mlentry = 0x185 p_msresv_2 = 0 } <-- and lots more output omitted --> > bd62*1000=K <-- multiple page frame number time page size (hex) bd62000 <-- here is physical address of page > bd62000+ea2,10/K <-- dump 16 64-bit hex values at physical address 0xbd62ea2: 2ccec81ec8b55 e8575653f0e48300 32c3815b00000000 5d89d46589003ea7 840ff6850c758be0 e445c7000007df 1216e8000000 dbe850e4458d5650 7d830cc483ffeeea 791840f00e4 c085e8458904468b 500c498b088b2474 8b17eb04c483d1ff e8458de05d8bd465 c483ffeeeac8e850 458b0000074ce904

Module 9 Managing Memory with DTrace and MDB

81

Software Memory Management

> bd62000+ea2,10/ai <-- data looks like code, lets try dumping as code 0xbd62ea2: 0xbd62ea2: pushq %rbp 0xbd62ea3: movl %esp,%ebp 0xbd62ea5: subl $0x2cc,%esp 0xbd62eab: andl $0xfffffff0,%esp 0xbd62eae: pushq %rbx 0xbd62eaf: pushq %rsi 0xbd62eb0: pushq %rdi 0xbd62eb1: call +0x5 <0xbd62eb6> 0xbd62eb6: popq %rbx 0xbd62eb7: addl $0x3ea732,%ebx 0xbd62ebd: movl %esp,-0x2c(%rbp) 0xbd62ec0: movl %ebx,-0x20(%rbp) 0xbd62ec3: movl 0xc(%rbp),%esi 0xbd62ec6: testl %esi,%esi 0xbd62ec8: je +0x7e5 <0xbd636ad> 0xbd62ece: movl $0x0,-0x1c(%rbp) > ffffffff81d6a040::context <-- change context from kernel to mozilla-bin debugger context set to proc ffffffff81d6a040, the address of the process > fb985ea2,10/ai <-- and dump from faulting virtual address 0xfb985ea2: 0xfb985ea2: pushq %rbp <-- looks like a match 0xfb985ea3: movl %esp,%ebp 0xfb985ea5: subl $0x2cc,%esp 0xfb985eab: andl $0xfffffff0,%esp 0xfb985eae: pushq %rbx 0xfb985eaf: pushq %rsi 0xfb985eb0: pushq %rdi 0xfb985eb1: call +0x5 <0xfb985eb6> 0xfb985eb6: popq %rbx 0xfb985eb7: addl $0x3ea732,%ebx 0xfb985ebd: movl %esp,-0x2c(%rbp) 0xfb985ec0: movl %ebx,-0x20(%rbp) 0xfb985ec3: movl 0xc(%rbp),%esi 0xfb985ec6: testl %esi,%esi 0xfb985ec8: je +0x7e5 <0xfb9866ad> 0xfb985ece: movl $0x0,-0x1c(%rbp) > 0::context debugger context set to kernel > ffffffff81d6a040::print proc_t p_as <-- get as for mozilla-bin

82

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Software Memory Management

p_as = 0xffffffff828b61d0 > fb985ea2::vtop -a ffffffff828b61d0 <-- check our work virtual fb985ea2 mapped to physical bd62ea2 <-- physical address matches

Once the segment is found, we print the segvn_data structure. In this segment, a vnode_t maps the segment data. The vnode_t contains a list of pages that "belong to" the vnode_t. We locate the page corresponding to the offset within the segment. Once the page_t is located, we have the page frame number. We then convert the page frame number to a physical address and examine some of the data at the address. It turns out this data is code. We then check the physical address by using the vtop (virtual-to-physical) mdb command.

Module 9 Managing Memory with DTrace and MDB

83

84

10
M O D U L E

1 0

Observing Processes in Zones With DTrace

Objectives
The objective of this module is to build on knowledge of DTrace to observe processes that run inside a zone.

85

Observing Processes in Zones With DTrace

Additional Resources
I

System Administration Guide: Solaris Containers-Resource Management and Solaris Zones

86

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Global and Non-Global Zones

Global and Non-Global Zones


Now that we have some knowledge of debugging applications, lets work on debugging applications that run in zones. Every OpenSolaris system contains a global zone. The global zone has a dual function. The global zone is both the default zone for the system and the zone used for system-wide administrative control. There are two types of non-global zone root le system models: sparse and whole root. The sparse root zone model optimizes the sharing of objects. The whole root zone model provides the maximum le system congurability. The scheduling class for a non-global zone is set to the scheduling class for the system. You can also set the scheduling class for a zone through the dynamic resource pools facility. If the zone is associated with a pool that has its pool.scheduler property set to a valid scheduling class, then processes running in the zone run in that scheduling class by default. Multiple zones can share a resource pool or in order to meet service guarantees, a single zone can be bound to a specic pool. By default, all zones including the global zone have one (1) fair share scheduler share assigned to them. Percentage of the CPU the zone is entitled to is the ratio of its shares and the total number of shares for all zones bound to a particular resource pool. The global administrator uses the zonecfg command to congure a zone by specifying various parameters for the zones virtual platform and application environment. The zone is then installed by the global administrator, who uses the zone administration command zoneadm to install software at the package level into the le system hierarchy established for the zone. The global administrator can log in to the installed zone by using the zlogin command. At rst login, the internal conguration for the zone is completed. The zoneadm command is then used to boot the zone.

Module 10 Observing Processes in Zones With DTrace

87

Global and Non-Global Zones

DTracing a Process Running in a Zone


This lab will focus on observing processes running in a zone. From the global zone, process tools like prstat(1M), ps(1) and truss(1) can be used to observe processes in other zones. DTrace may be used from the global zone and supports a zonename variable and the pr_zoneid eld in psinfo_t for use with the proc provider.
1 2

Open a terminal window. Log into the global zone:


% zlogin password: #

Count the number of I/O operations per zone:


# dtrace -n io:::start{@[zonename] = count()}

88

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

11
M O D U L E

1 1

Conguring Filesystems With ZFS

Objectives
The objective of this lesson is to provide an introduction to ZFS by showing you how to create a simple ZFS pool with a mirrored lesystem.

89

Conguring Filesystems With ZFS

Additional Resources
ZFS Administration Guide and man pages: http://opensolaris.org/os/community/zfs/docs/

90

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Creating Pools With Mounted Filesystems

Creating Pools With Mounted Filesystems


Each storage pool is comprised of one or more virtual devices, which describe the layout of physical storage and its fault characteristics. The most basic building block for a storage pool is a piece of physical storage. This can be any block device of at least 128 Mbytes in size. Typically, this is a hard drive that is visible to the system in the /dev/dsk directory. A storage device can be a whole disk (c0t0d0) or an individual slice (c0t0d0s7). The recommended mode of operation is to use an entire disk, in which case the disk does not need to be specially formatted. ZFS formats the disk using an EFI label to contain a single, large slice. In this module, well start by learning about mirrored storage pool conguration. Then well show you how to congure RAID-Z. In traditional storage congurations which use partitions or volumes, the storage is fragmented across disks. ZFS uses pooled storage to eliminate the management problems associated with volumes and to enable all storage to be shared. The value of shared storage is the ability to repair damaged data.

Module 11 Conguring Filesystems With ZFS

91

Creating Pools With Mounted Filesystems

Creating Mirrored Storage Pools


The objective of this lab exercise is to create and list a mirrored storage pool using the zpool command. ZFS is easy, so lets get on with it! Its time to create your rst pool:
1 2

Open a terminal window. Create a single-disk storage pool named tank:


# zpool create tank c1t2d0

You now have a single-disk storage pool named tank, with a single lesystem mounted at /tank.
3

Validate that the pool was created:


# zpool list NAME tank SIZE 80.0G USED 22.3G AVAIL 47.7G CAP HEALTH 28% ONLINE ALTROOT -

Create a mirror of tank:


# zpool create tank mirror c1t2d0 c2t2d0

The storage pool is mirrored on c2t2d0.

92

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Creating Pools With Mounted Filesystems

Creating a Filesystem and /home Directories


The objective of this lab exercise is to learn how to set up a lesystem with several /home directories. In this lab, well use the zfs command to create a lesystem and set its mountpoint.
1 2

Open a terminal window. Create the /var/mail lesystem:


# zfs create tank/mail

Set the mount point for the /var/mail lesystem:


# zfs set mountpoint=/var/mail tank/mail

Create the home directory:


# zfs create tank/home

Then, set the mount point for the home directory:


# zfs set mountpoint=/export/home tank/home

Finally, create home directories for all of your developers:


# zfs create tank/home/developer1 # zfs create tank/home/developer2 # zfs create tank/home/developer3 # zfs create tank/home/developer4

The mountpoint property is inherited as a pathname prex. That is, tank/home/developer1 is automatically mounted at /export/home/developer1 because tank/home is mounted at /export/home.

Module 11 Conguring Filesystems With ZFS

93

Creating Pools With Mounted Filesystems

Conguring RAID-Z
The objective of this lab exercise is to introduce you to the RAID-Z conguration. You might want to congure RAID-Z instead of mirrored pools for greater redundancy. You need at least two disks for a RAID-Z conguration. Other than that, no special hardware is required to create a RAID-Z conguration. Creating a RAID-Z pool is identical to a mirrored pool, except that the raidz keyword is used instead of mirror.
1 2

Open a terminal window. Create a pool with a single RAID-Z device consisting of 5 disk slices:
# zpool create tank raidz c0t0d0s0 c0t0d1s0 c0t0d2s0 c0t0d3s0 c0t0d4s0

In the above example, the disk must have been pre-formatted to have an appropriately sized slice zero. Disks can be specied using their full path. /dev/dsk/c0t0d4s0 is identical to c0t0d4s0 by itself. Note that there is no requirement to use disk slices in a RAID-Z conguration. The above command is just an example of using disk slices in a storage pool.

94

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

12
M O D U L E

1 2

Writing a Template Character Device Driver

Objectives
This module shows you how to develop a very simple, working driver. This module explains how to write the driver and conguration le, compile the driver, load the driver, and test the driver. The driver that is shown in this module is a pseudo device driver that merely writes a message to a system log every time an entry point is entered. This driver demonstrates the minimum functionality that any character driver must implement. You can use this driver as a template for building a complex driver.

95

Writing a Template Character Device Driver

Additional Resources
I I

Writing Device Drivers. Sun Microsystems, Inc., 2005. Solaris Modular Debugger Guide. Sun Microsystems, Inc., 2005.

96

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Overview of the Template Driver Example

Overview of the Template Driver Example


This example guides you through the following steps: 1. Create a directory where you can develop your driver and open a new text le named dummy.c. 2. Write the entry points for loadable module conguration: _init(9E), _info(9E), and _fini(9E). 3. Write the entry points for autoconguration: attach(9E), detach(9E), getinfo(9E), and prop_op(9E). 4. Write the entry points for user context: open(9E), close(9E), read(9E), and write(9E). 5. Dene the data structures: the character and block operations structure cb_ops(9S), the device operations structure dev_ops(9S), and the module linkage structures modldrv(9S) and modlinkage(9S). 6. Create the driver conguration le dummy.conf. 7. Build and install the driver. 8. Test the driver by loading the driver, reading from and writing to the device node, and unloading the driver.

Module 12 Writing a Template Character Device Driver

97

Writing the Template Driver

Writing the Template Driver


This section describes the entry points and data structures that are included in this driver and shows you how to dene them. All of these data structures and almost all of these entry points are required for any character device driver. This section describes the following entry points and data structures:
I I I I I I

Loadable module conguration entry points Autoconguration entry points User context entry points Character and block operations structure Device operations structure Module linkage structures

First, create a directory where you can develop your driver. This driver is named dummy because this driver does not do any real work. Next, open a new text le named dummy.c.

Writing the Loadable Module Conguration Entry Points


Every kernel module of any type must dene at least the following three loadable module conguration entry points:
I

The _init(9E) routine initializes a loadable module. The _init(9E) routine must at least call the mod_install(9F) function and return the success or failure value that is returned by mod_install(9F). The _info(9E) routine returns information about a loadable module. The _info(9E) routine must at least call the mod_info(9F) function and return the value that is returned by mod_info(9F). The _fini(9E) routine prepares a loadable module for unloading. The _fini(9E) routine must at least call the mod_remove(9F) function and return the success or failure value that is returned by mod_remove(9F). When mod_remove(9F) is successful, the _fini(9E) routine must undo everything that the _init(9E) routine did.

The mod_install(9F), mod_info(9F), and mod_remove(9F) functions are used in exactly the same way in every driver, regardless of the functionality of the driver. You do not need to investigate what the values of the arguments of these functions should be. You can copy these function calls from this example and paste them into every driver you write. In this section, the following code is added to the dummy.c source le:
98 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

/* Loadable module configuration entry points */ int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); } int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info"); return(mod_info(&ml, modinfop)); } int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); }

Declaring the Loadable Module Conguration Entry Points


The _init(9E), _info(9E), and _fini(9E) routine names are not unique to any particular kernel module. You customize the behavior of these routines when you dene them in your module, but the names of these routines are not unique. These three routines are declared in the modctl.h header le. You need to include the modctl.h header le in your dummy.c le. Do not declare these three routines in dummy.c.

Dening the Module Initialization Entry Point


The _init(9E) routine returns type int and takes no arguments. The _init(9E) routine must call the mod_install(9F) function and return the success or failure value that is returned by mod_install(9F). The mod_install(9F) function takes an argument that is a modlinkage(9S) structure. This driver is supposed to write a message each time an entry point is entered. Use the cmn_err(9F) function to write a message to a system log. The cmn_err(9F) function usually is used to report an error condition. The cmn_err(9F) function also is useful for debugging in the same way that you might use print statements in a user program. The cmn_err(9F) function requires you to include the cmn_err.h header le, the ddi.h header le, and the sunddi.h header le. The cmn_err(9F) function takes two arguments. The rst
Module 12 Writing a Template Character Device Driver 99

Writing the Template Driver

argument is a constant that indicates the severity of the error message. The message written by this driver is not an error message but is simply a test message. Use CE_NOTE for the value of this severity constant. The second argument the cmn_err(9F) function takes is a string message. The following code is the _init(9E) routine that you should enter into your dummy.c le. The ml structure is the modlinkage(9S) structure.
int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); }

Dening the Module Information Entry Point


The _info(9E) routine returns type int and takes an argument that is a pointer to an opaque modinfo structure. The _info(9E) routine must return the value that is returned by the mod_info(9F) function. The mod_info(9F) function takes two arguments. The rst argument to mod_info(9F) is a modlinkage(9S) structure. The second argument to mod_info(9F) is the same modinfo structure pointer that is the argument to the _info(9E) routine. The mod_info(9F) function returns the module information or returns zero if an error occurs. Use the cmn_err(9F) function to write a message to the system log in the same way that you used the cmn_err(9F) function in your _init(9E) entry point. The following code is the _info(9E) routine that you should enter into your dummy.c le. The modinfop argument is a pointer to an opaque structure that the system uses to pass module information.
int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info"); return(mod_info(&ml, modinfop)); }

Dening the Module Unload Entry Point


The _fini(9E) routine returns type int and takes no arguments. The _fini(9E) routine must call the mod_remove(9F) function and return the success or failure value that is returned by mod_remove(9F).
100 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

When mod_remove(9F) is successful, the _fini(9E) routine must undo everything that the _init(9E) routine did. The _fini(9E) routine must call mod_remove(9F) because the _init(9E) routine called mod_install(9F). The _fini(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the _init(9E) routine. The _fini(9E) routine can be called at any time when a module is loaded. In normal operation, the _fini(9E) routine often fails. This behavior is normal because the kernel allows the module to determine whether the module can be unloaded. If mod_remove(9F) is successful, the module determines that devices were detached, and the module can be unloaded. If mod_remove(9F) fails, the module determines that devices were not detached, and the module cannot be unloaded. The following actions take place when mod_remove(9F) is called:
I

The kernel checks whether this driver is busy. This driver is busy if one of the following conditions is true:
I I

A device node that is managed by this driver is open. Another module that depends on this driver is open. A module depends on this driver if the module was linked using the -N option with this driver named as the argument to that -N option. See the ld(1) man page for more information.

I I

If the driver is busy, then mod_remove(9F) fails and _fini(9E) fails. If the driver is not busy, then the kernel calls the detach(9E) entry point of the driver.
I I

If detach(9E) fails, then mod_remove(9F) fails and _fini(9E) fails. If detach(9E) succeeds, then mod_remove(9F) succeeds, and _fini(9E) continues its cleanup work.

The mod_remove(9F) function takes an argument that is a modlinkage(9S) structure. Use the cmn_err(9F) function to write a message to the system log in the same way that you used the cmn_err(9F) function in your _init(9E) entry point. The following code is the _fini(9E) routine that you should enter into your dummy.c le.
int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); }

Module 12 Writing a Template Character Device Driver

101

Writing the Template Driver

Including Loadable Module Conguration Header Files


The _init(9E), _info(9E), _fini(9E), and mod_install(9F) functions require you to include the modctl.h header le. The cmn_err(9F) function requires you to include the cmn_err.h header le, the ddi.h header le, and the sunddi.h header le. The following header les are required by the three loadable module conguration routines that you have written in this section. Include this code near the top of your dummy.c le.
#include #include #include #include <sys/modctl.h> <sys/cmn_err.h> <sys/ddi.h> <sys/sunddi.h> /* /* /* /* used used used used by by by by _init, _info, _fini */ all entry points for this driver */ all entry points for this driver */ all entry points for this driver */

Writing the Autoconguration Entry Points


Every character driver must dene at least the following autoconguration entry points. The kernel calls these routines when the device driver is loaded.
I

The attach(9E) routine must call ddi_create_minor_node(9F). The ddi_create_minor_node(9F) function provides the information the system needs to create the device les. The detach(9E) routine must call ddi_remove_minor_node(9F) to deallocate everything that was allocated by ddi_create_minor_node(9F). The detach(9E) routine must undo everything that the attach(9E) routine did. The getinfo(9E) routine returns requested device driver information through one of its arguments. The prop_op(9E) routine returns requested device driver property information through a pointer. You can call the ddi_prop_op(9F) function instead of writing your own prop_op(9E) entry point. Use the prop_op(9E) entry point to customize the behavior of the ddi_prop_op(9F) function.

In this section, the following code is added:


/* Device autoconfiguration entry points */ static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0)

102

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

!= DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH: dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,

Module 12 Writing a Template Character Device Driver

103

Writing the Template Driver

int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); }

Declaring the Autoconguration Entry Points


The attach(9E), detach(9E), getinfo(9E), and prop_op(9E) entry point routines need to be uniquely named for this driver. Choose a prex to use with each entry point routine.
Note By convention, the prex used for function and data names that are unique to this driver

is either the name of this driver or an abbreviation of the name of this driver. Use the same prex throughout the driver. This practice makes debugging much easier. In the example shown in this module, dummy_ is used for the prex to each function and data name that is unique to this example. The following declarations are the autoconguration entry point declarations you should have in your dummy.c le. Note that each of these functions is declared static.
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp);

Dening the Device Attach Entry Point


The attach(9E) routine returns type int. The attach(9E) routine must return either DDI_SUCCESS or DDI_FAILURE. These two constants are dened in sunddi.h. All of the autoconguration entry point routines except for prop_op(9E) return either DDI_SUCCESS or DDI_FAILURE. The attach(9E) routine takes two arguments. The rst argument is a pointer to the dev_info structure for this driver. All of the autoconguration entry point routines take a dev_info argument. The second argument is a constant that species the attach type. The value that is passed through this second argument is either DDI_ATTACH or DDI_RESUME. Every attach(9E) routine must dene behavior for at least DDI_ATTACH. The DDI_ATTACH code must initialize a device instance. In a realistic driver, you dene and manage multiple instances of the driver by using a state structure and the ddi_soft_state(9F) functions. Each instance of the driver has its own copy of the state
104 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

structure that holds data specic to that instance. One of the pieces of data that is specic to each instance is the device instance pointer. Each instance of the device driver is represented by a separate device le in /devices. Each device instance le is pointed to by a separate device instance pointer. This dummy driver allows only one instance. Because this driver allows only one instance, this driver does not use a state structure. This driver still must declare a device instance pointer and initialize the pointer value in the attach(9E) routine. Enter the following code near the beginning of dummy.c to declare a device instance pointer for this driver:
dev_info_t *dummy_dip; /* keep track of one instance */

The following code is the dummy_attach() routine that you should enter into your dummy.c le.
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } }

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_ATTACH behavior. Within the DDI_ATTACH code, rst assign the device instance pointer from the dummy_attach() argument to the dummy_dip variable that you declared above. You need to save this pointer value in the global variable so that you can use this pointer to get information about this instance from dummy_getinfo() and detach this instance in dummy_detach(). In this dummy_attach() routine, the device instance pointer is used by the ddi_get_instance(9F) function to return the instance number. The device instance pointer and the instance number both are used by ddi_create_minor_node(9F) to create a new device node.
Module 12 Writing a Template Character Device Driver 105

Writing the Template Driver

A realistic driver probably would use the ddi_soft_state(9F) functions to create and manage a device node. This dummy driver uses the ddi_create_minor_node(9F) function to create a device node. The ddi_create_minor_node(9F) function takes six arguments. The rst argument to the ddi_create_minor_node(9F) function is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of this minor node. The third argument is S_IFCHR if this device is a character minor device or is S_IFBLK if this device is a block minor device. This dummy driver is a character driver. The fourth argument to the ddi_create_minor_node(9F) function is the minor number of this minor device. This number is also called the instance number. The ddi_get_instance(9F) function returns this instance number. The fth argument to the ddi_create_minor_node(9F) function is the node type. The ddi_create_minor_node(9F) man page lists the possible node types. The DDI_PSEUDO node type is for pseudo devices. The sixth argument to the ddi_create_minor_node(9F) function species whether this is a clone device. This is not a clone device, so set this argument value to 0. If the ddi_create_minor_node(9F) call is not successful, write a message to the system log and return DDI_FAILURE. If the ddi_create_minor_node(9F) call is successful, return DDI_SUCCESS. If this dummy_attach() routine receives any cmd other than DDI_ATTACH, return DDI_FAILURE.

Dening the Device Detach Entry Point


The detach(9E) routine takes two arguments. The rst argument is a pointer to the dev_info structure for this driver. The second argument is a constant that species the detach type. The value that is passed through this second argument is either DDI_DETACH or DDI_SUSPEND. Every detach(9E) routine must dene behavior for at least DDI_DETACH. The DDI_DETACH code must undo everything that the DDI_ATTACH code did. In the DDI_ATTACH code in your attach(9E) routine, you saved the address of a new dev_info structure and you called the ddi_create_minor_node(9F) function to create a new node. In the DDI_DETACH code in this detach(9E) routine, you need to reset the variable that pointed to the dev_info structure for this node. You also need to call the ddi_remove_minor_node(9F) function to remove this node. The detach(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the attach(9E) routine. The following code is the dummy_detach() routine that you should enter into your dummy.c le.
static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH:

106

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } }

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_DETACH behavior. Within the DDI_DETACH code, rst reset the dummy_dip variable that you set in dummy_attach() above. You cannot reset this device instance pointer unless you remove all instances of the device. This dummy driver supports only one instance. Next, call the ddi_remove_minor_node(9F) function to remove this device node. The ddi_remove_minor_node(9F) function takes two arguments. The rst argument is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of the minor node you want to remove. If the value of the minor node argument is NULL, then ddi_remove_minor_node(9F) removes all instances of this device. Because the DDI_DETACH code of this driver always removes all instances, this dummy driver supports only one instance. If the value of the cmd argument to this dummy_detach() routine is DDI_DETACH, remove all instances of this device and return DDI_SUCCESS. If this dummy_detach() routine receives any cmd other than DDI_DETACH, return DDI_FAILURE.

Dening the Get Driver Information Entry Point


The getinfo(9E) routine takes a pointer to a device number and returns a pointer to a device information structure or returns a device instance number. The return value of the getinfo(9E) routine is DDI_SUCCESS or DDI_FAILURE. The pointer or instance number requested from the getinfo(9E) routine is returned through a pointer argument. The getinfo(9E) routine takes four arguments. The rst argument is a pointer to the dev_info structure for this driver. This dev_info structure argument is obsolete and is no longer used by the getinfo(9E) routine. The second argument to the getinfo(9E) routine is a constant that species what information the getinfo(9E) routine must return. The value of this second argument is either DDI_INFO_DEVT2DEVINFO or DDI_INFO_DEVT2INSTANCE. The third argument to the getinfo(9E) routine is a pointer to a device number. The fourth argument is a pointer to the place where the getinfo(9E) routine must store the requested information. The information stored at this location depends on the value you passed in the second argument to the getinfo(9E) routine.
Module 12 Writing a Template Character Device Driver 107

Writing the Template Driver

The following table describes the relationship between the second and fourth arguments to the getinfo(9E) routine.
TABLE 121 Get Driver Information Entry Point Arguments cmd arg resultp

DDI_INFO_DEVT2DEVINFO DDI_INFO_DEVT2INSTANCE

Device number Device number

Device information structure pointer Device instance number

The following code is the dummy_getinfo() routine that you should enter into your dummy.c le.
static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } }

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_INFO_DEVT2DEVINFO behavior. A realistic driver would use arg to get the instance number of this device node. A realistic driver would then call the ddi_get_soft_state(9F) function and return the device information structure pointer from that state structure. This dummy driver supports only one instance and does not use a state structure. In the DDI_INFO_DEVT2DEVINFO code of this dummy_getinfo() routine, simply return the one device information structure pointer that the dummy_attach() routine saved. Next, provide DDI_INFO_DEVT2INSTANCE behavior. Within the DDI_INFO_DEVT2INSTANCE code, simply return 0. This dummy driver supports only one instance. The instance number of that one instance is 0.

108

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

Dening the Report Driver Property Information Entry Point


The prop_op(9E) entry point is required for every driver. If your driver does not need to customize the behavior of the prop_op(9E) entry point, then your driver can use the ddi_prop_op(9F) function for the prop_op(9E) entry point. Drivers that create and manage their own properties need a custom prop_op(9E) routine. This dummy driver uses a prop_op(9E) routine to call cmn_err(9F) before calling the ddi_prop_op(9F) function. The prop_op(9E) entry point and the ddi_prop_op(9F) function both require that you include the types.h header le. The prop_op(9E) entry point and the ddi_prop_op(9F) function both take the same seven arguments. These arguments are not discussed here because this dummy driver does not create and manage its own properties. See the prop_op(9E) man page to learn about the prop_op(9E) arguments. The following code is the dummy_prop_op() routine that you should enter into your dummy.c le.
static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); }

First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then call the ddi_prop_op(9F) function with exactly the same arguments as the dummy_prop_op() function.

Including Autoconguration Header Files


All of the autoconguration entry point routines and all of the user context entry point routines require that you include the ddi.h and sunddi.h header les. You already included these two header les for the cmn_err(9F) function. The ddi_create_minor_node(9F) function requires the stat.h header le. The dummy_attach() routine calls the ddi_create_minor_node(9F) function. The prop_op(9E) and the ddi_prop_op(9F) functions require the types.h header le. The following code is the list of header les that you now should have included in your dummy.c le for the four autoconguration routines you have written in this section and the three loadable module conguration routines you wrote in the previous section.
#include <sys/modctl.h> /* used by _init, _info, _fini */ #include <sys/types.h> /* used by prop_op, ddi_prop_op */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */

Module 12 Writing a Template Character Device Driver

109

Writing the Template Driver

#include <sys/cmn_err.h> /* #include <sys/ddi.h> /* /* #include <sys/sunddi.h> /* /* /*

used by all entry points for this driver */ used by all entry points for this driver */ also used by ddi_get_instance, ddi_prop_op */ used by all entry points for this driver */ also used by ddi_create_minor_node, */ ddi_get_instance, and ddi_prop_op */

Writing the User Context Entry Points


User context entry points correspond closely to system calls. When a system call opens a device le, then the open(9E) routine in the driver for that device is called. All character and block drivers must dene the open(9E) user context entry point. However, the open(9E) routine can be nulldev(9F). The close(9E), read(9E), and write(9E) user context routines are optional.
I I

The open(9E) routine gains access to the device. The close(9E) routine relinquishes access to the device. The close(9E) routine must undo everything that the open(9E) routine did. The read(9E) routine reads data from the device node. The write(9E) routine writes data to the device node.

I I

In this section, the following code is added:


/* Use context entry points */ static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; } static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; } static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read");

110

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

return DDI_SUCCESS; } static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }

Declaring the User Context Entry Points


The user context entry point routines need to be uniquely named for this driver. Use the same prex for each of the user context entry points that you used for each of the autoconguration entry point routines. The following declarations are the entry point declarations you should have in your dummy.c le:
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp); static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred); static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred); static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp); static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp);

Dening the Open Device Entry Point


The open(9E) routine returns type int. The open(9E) routine should return either DDI_SUCCESS or the appropriate error number. The open(9E) routine takes four arguments. This dummy driver is so simple that this dummy_open() routine does not use any of the open(9E) arguments. The following code is the dummy_open() routine that you should enter into your dummy.c le. Write a message to the system log and return success.
static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; }

Module 12 Writing a Template Character Device Driver

111

Writing the Template Driver

Dening the Close Device Entry Point


The close(9E) routine returns type int. The close(9E) routine should return either DDI_SUCCESS or the appropriate error number. The close(9E) routine takes four arguments. This dummy driver is so simple that this dummy_close() routine does not use any of the close(9E) arguments. The close(9E) routine must undo everything that the open(9E) routine did. The close(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the open(9E) routine. In this dummy driver, the open(9E) routine is so simple that nothing needs to be reclaimed or undone in the close(9E) routine. The following code is the dummy_close() routine that you should enter into your dummy.c le. Write a message to the system log and return success.
static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; }

Dening the Read Device Entry Point


The read(9E) routine returns type int. The read(9E) routine should return either DDI_SUCCESS or the appropriate error number. The read(9E) routine takes three arguments. This dummy driver is so simple that this dummy_read() routine does not use any of the read(9E) arguments. The following code is the dummy_read() routine that you should enter into your dummy.c le. Write a message to the system log and return success.
static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read"); return DDI_SUCCESS; }

Dening the Write Device Entry Point


The write(9E) routine returns type int. The write(9E) routine should return either DDI_SUCCESS or the appropriate error number. The write(9E) routine takes three arguments. This dummy driver is so simple that this dummy_write() routine does not use any of the write(9E) arguments.
112 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

The following code is the dummy_write() routine that you should enter into your dummy.c le. Write a message to the system log and return success.
static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }

Including User Context Header Files


The four user context entry point routines require your module to include several header les. You already have included the types.h header le, the ddi.h header le, and the sunddi.h header le. You need to include the file.h, errno.h, open.h, cred.h, and uio.h header les. The following code is the list of header les that you now should have included in your dummy.c le for all the entry points you have written in this section and the previous two sections:
#include <sys/modctl.h> /* /* #include <sys/types.h> /* /* #include <sys/file.h> /* #include <sys/errno.h> /* #include <sys/open.h> /* #include <sys/cred.h> /* #include <sys/uio.h> /* #include <sys/stat.h> /* #include <sys/cmn_err.h> /* #include <sys/ddi.h> /* /* /* #include <sys/sunddi.h> /* /* /* used by modlinkage, modldrv, _init, _info, */ and _fini */ used by open, close, read, write, prop_op, */ and ddi_prop_op */ used by open, close */ used by open, close, read, write */ used by open, close, read, write */ used by open, close, read */ used by read */ defines S_IFCHR used by ddi_create_minor_node */ used by all entry points for this driver */ used by all entry points for this driver */ also used by ddi_get_instance and */ ddi_prop_op */ used by all entry points for this driver */ also used by ddi_create_minor_node, */ ddi_get_instance, and ddi_prop_op */

Writing the Driver Data Structures


All of the data structures described in this section are required for every device driver. All drivers must dene a dev_ops(9S) device operations structure. Because the dev_ops(9S) structure includes a pointer to the cb_ops(9S) character and block operations structure, you must dene the cb_ops(9S) structure rst. The modldrv(9S) linkage structure for loadable
Module 12 Writing a Template Character Device Driver 113

Writing the Template Driver

drivers includes a pointer to the dev_ops(9S) structure. The modlinkage(9S) module linkage structure includes a pointer to the modldrv(9S) structure. Except for the loadable module conguration entry points, all of the required entry points for a driver are initialized in the character and block operations structure or in the device operations structure. Some optional entry points and other related data also are initialized in these data structures. Initializing the entry points in these data structures enables the driver to be dynamically loaded. The loadable module conguration entry points are not initialized in driver data structures. The _init(9E), _info(9E), and _fini(9E) entry points are required for all kernel modules and are not specic to device driver modules. In this section, the following code is added:
/* cb_ops structure */ static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ }; /* dev_ops structure */ static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, nulldev, /* no identify - nulldev returns 0 */ nulldev, /* no probe */ dummy_attach,

114

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev /* no power */ }; /* modldrv structure */ static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; /* modlinkage structure */ static struct modlinkage ml = { MODREV_1, &md, NULL }; /* dev_info structure */ dev_info_t *dummy_dip; /* keep track of one instance */

Dening the Character and Block Operations Structure


The cb_ops(9S) structure initializes standard character and block interfaces. See the cb_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the cb_ops(9S) structure. See the description that follows the code sample. When you name this structure, use the same dummy_ prex that you used for the names of the autoconguration routines and the names of the user context routines. Prepend the static type modier to the declaration. The following code is the cb_ops(9S) structure that you should enter into your dummy.c le:
static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write,

Module 12 Writing a Template Character Device Driver

115

Writing the Template Driver

nodev, nodev, nodev, nodev, nochpoll, dummy_prop_op, NULL, D_NEW | D_MP, CB_REV, nodev, nodev };

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

no ioctl */ no devmap */ no mmap */ no segmap */ returns ENXIO for non-pollable devices */ streamtab struct; if not NULL, all above */ fields are ignored */ compatibility flags: see conf.h */ cb_ops revision number */ no aread */ no awrite */

Enter the names of the open(9E) and close(9E) entry points for this driver as the values of the rst two elements of this structure. Enter the names of the read(9E) and write(9E) entry points for this driver as the values of the sixth and seventh elements of this structure. Enter the name of the prop_op(9E) entry point for this driver as the value of the thirteenth element in this structure. The strategy(9E), print(9E), and dump(9E) routines are for block drivers only. This dummy driver does not dene these three routines because this driver is a character driver. This driver does not dene an ioctl(9E) entry point because this driver does not use I/O control commands. This driver does not dene devmap(9E), mmap(9E), or segmap(9E) entry points because this driver does not support memory mapping. This driver does not does not dene aread(9E) or awrite(9E) entry points because this driver does not perform any asynchronous reads or writes. Initialize all of these unused function elements to nodev(9F). The nodev(9F) function returns the ENXIO error code. Specify the nochpoll(9F) function for the chpoll(9E) element of the cb_ops(9S) structure because this driver is not for a pollable device. Specify NULL for the streamtab(9S) STREAMS entity declaration structure because this driver is not a STREAMS driver. The compatibility ags are dened in the conf.h header le. The D_NEW ag means this driver is a new-style driver. The D_MP ag means this driver safely allows multiple threads of execution. All drivers must be multithreaded-safe, and must specify this D_MP ag. The D_64BIT ag means this driver supports 64-bit offsets and block numbers. See the conf.h header le for more compatibility ags. The CB_REV element of the cb_ops(9S) structure is the cb_ops(9S) revision number. CB_REV is dened in the devops.h header le.

116

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

Dening the Device Operations Structure


The dev_ops(9S) structure initializes interfaces that are used for operations such as attaching and detaching the driver. See the dev_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the dev_ops(9S) structure. See the description that follows the code sample. When you name this structure, use the same dummy_ prex that you used for the names of the autoconguration routines and the names of the user context routines. Prepend the static type modier to the declaration. The following code is the dev_ops(9S) structure that you should enter into your dummy.c le:
static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, nulldev, /* no identify - nulldev returns 0 */ nulldev, /* no probe */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev /* no power */ };

The DEVO_REV element of the dev_ops(9S) structure is the driver build version. DEVO_REV is dened in the devops.h header le. The second element in this structure is the driver reference count. Initialize this value to zero. The driver reference count is the number of instances of this driver that are currently open. The driver cannot be unloaded if any instances of the driver are still open. The next six elements of the dev_ops(9S) structure are the names of the getinfo(9E), identify(9E), probe(9E), attach(9E), detach(9E), and reset() functions for this particular driver. The identify(9E) function is obsolete. Initialize this structure element to nulldev(9F). The probe(9E) function determines whether the corresponding device exists and is valid. This dummy driver does not dene a probe(9E) function. Initialize this structure element to nulldev. The nulldev(9F) function returns success. The reset() function is obsolete. Initialize the reset() function to nodev(9F). The next element of the dev_ops(9S) structure is a pointer to the cb_ops(9S) structure for this driver. Enter &dummy_cb_ops for the value of the pointer to the cb_ops(9S) structure. The next element of the dev_ops(9S) structure is a pointer to the bus operations structure. Only nexus drivers have bus operations structures. This dummy driver is not a nexus driver. Set this value to NULL because this driver is a leaf driver.
Module 12 Writing a Template Character Device Driver 117

Writing the Template Driver

The last element of the dev_ops(9S) structure is the name of the power(9E) routine for this driver. The power(9E) routine operates on a hardware device. This driver does not drive a hardware device. Set the value of this structure element to nodev.

Dening the Module Linkage Structures


Two other module loading structures are required for every driver. The modlinkage(9S) module linkage structure is used by the _init(9E), _info(9E), and _fini(9E) routines to install, remove, and retrieve information from a module. The modldrv(9S) linkage structure for loadable drivers exports driver-specic information to the kernel. See the man pages for each structure to learn what each element is and what the value of each element should be. The following code denes the modldrv(9S) and modlinkage(9S) structures for the driver shown in this module:
static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL };

The rst element in the modldrv(9S) structure is a pointer to a structure that tells the kernel what kind of module this is. Set this value to the address of the mod_driverops structure. The mod_driverops structure tells the kernel that the dummy.c module is a loadable driver module. The mod_driverops structure is declared in the modctl.h header le. You already included the modctl.h header le in your dummy.c le, so do not declare the mod_driverops structure in dummy.c. The mod_driverops structure is dened in the modctl.c source le. The second element in the modldrv(9S) structure is a string that describes this module. Usually this string contains the name of this module and the version number of this module. The last element of the modldrv(9S) structure is a pointer to the dev_ops(9S) structure for this driver. The rst element in the modlinkage(9S) structure is the revision number of the loadable modules system. Set this value to MODREV_1. The next element of the modlinkage(9S) structure is the address of a null-terminated array of pointers to linkage structures. Driver modules have only one linkage structure. Enter the address of the md structure for the value of this element of the modlinkage(9S) structure. Enter the value NULL to terminate this list of linkage structures.
118 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Writing the Template Driver

Including Data Structures Header Files


The cb_ops(9S) and dev_ops(9S) structures require you to include the conf.h and devops.h header les. The modlinkage(9S) and modldrv(9S) structures require you to include the modctl.h header le. You already included the modctl.h header le for the loadable module conguration entry points. The following code is the complete list of header les that you now should have included in your dummy.c le:
#include <sys/devops.h> /* used by dev_ops */ #include <sys/conf.h> /* used by dev_ops and cb_ops */ #include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */ /* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */ #include <sys/cred.h> /* used by open, close, read */ #include <sys/uio.h> /* used by read */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_get_instance, and */ /* ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */

Module 12 Writing a Template Character Device Driver

119

Writing the Device Conguration File

Writing the Device Conguration File


This driver requires a conguration le. The minimum information that a conguration le must contain is the name of the device node and the name or type of the devices parent. In this simple example, the node name of the device is the same as the le name of the driver. Create a le named dummy.conf in your working directory. Put the following single line of information into dummy.conf:
name="dummy" parent="pseudo";

120

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Building and Installing the Template Driver

Building and Installing the Template Driver


This section shows you how to build and install the driver for a 32-bit platform. Compile and link the driver. Use the -D_KERNEL option to indicate that this code denes a kernel module. The following example shows compiling and linking for a 32-bit architecture using the Sun Studio C compiler:
% cc -D_KERNEL -c dummy.c % ld -r -o dummy dummy.o

Make sure you are user root when you install the driver. Install drivers in the /tmp directory until you are nished modifying and testing the _info(), _init(), and attach() routines. Copy the driver binary to the /tmp directory. Link to the driver from the kernel driver directory.
# cp dummy /tmp

Link to the following directory for a 32-bit architecture:


# ln -s /tmp/dummy /usr/kernel/drv/dummy

Copy the conguration le to the kernel driver area of the system.


# cp dummy.conf /usr/kernel/drv

Module 12 Writing a Template Character Device Driver

121

Testing the Template Driver

Testing the Template Driver


This dummy driver merely writes a message to a system log each time an entry point routine is entered. To test this driver, watch for these messages to conrm that each entry point routine is successfully entered. The cmn_err(9F) function writes low priority messages such as the messages dened in this dummy driver to /dev/log. The syslogd(1M) daemon reads messages from /dev/log and writes low priority messages to /var/adm/messages. In a separate window, enter the following command and monitor the output as you perform the tests described in the remainder of this section:
% tail -f /var/adm/messages

Adding the Template Driver


Make sure you are user root when you add the driver. Use the add_drv(1M) command to add the driver:
# add_drv dummy

You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 513080 kern.notice] NOTICE: Inside _info date time machine dummy: [ID 874762 kern.notice] NOTICE: Inside _init date time machine dummy: [ID 678704 kern.notice] NOTICE: Inside dummy_attach

The _info(9E), _init(9E), and attach(9E) entry points are called in that order when you add a driver. The dummy driver has been added to the /devices directory:
% ls -l /devices/pseudo | grep dummy drwxr-xr-x 2 root sys 512 date time dummy@0 crw------- 1 root sys 92, 0 date time dummy@0:0

The dummy driver also is the most recent module listed by modinfo(1M):
% modinfo Id Loadaddr 180 ed192b70 Size Info Rev Module Name 544 92 1 dummy (dummy driver)

The module name, dummy driver, is the value you entered for the second member of the modldrv(9S) structure. The value 92 is the major number of this module.
122 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Testing the Template Driver

% grep dummy /etc/name_to_major dummy 92

The Loadaddr address of ed192b70 is the address of the rst instruction in the dummy driver. This address might be useful, for example, in debugging.
% mdb -k > dummy_init $m BASE LIMIT ed192b70 ed192ff0 > $q

SIZE NAME 480 dummy

The dummy driver also is the most recent module listed by prtconf(1M) in the pseudo device section:
% prtconf -P pseudo, instance #0 dummy, instance #0 (driver not attached)

A driver is automatically loaded when a device that the driver manages is accessed. A driver might be automatically unloaded when the driver is not in use. If your driver is in the /devices directory but modinfo(1M) does not list your driver, you can use either of the following methods to load your driver:
I I

Use the modload(1M) command. Access the device. The driver is loaded automatically when a device that the driver manages is accessed. The following section describes how to access the dummy device.

Reading and Writing the Device


Make sure you are user root when you perform the tests described in this section. If you are not user root, you will receive Permission denied error messages when you try to access the /devices/pseudo/dummy@0:0 special le. Test reading from the device. Your dummy device probably is named /devices/pseudo/dummy@0:0. The following command reads from your dummy device even if it has a slightly different name:
# cat /devices/pseudo/dummy*

You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 136952 kern.notice] NOTICE: Inside dummy_open date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 891851 kern.notice] NOTICE: Inside dummy_prop_op

Module 12 Writing a Template Character Device Driver

123

Testing the Template Driver

date date date date date

time time time time time

machine machine machine machine machine

dummy: dummy: dummy: dummy: dummy:

[ID [ID [ID [ID [ID

623947 891851 623947 709590 550206

kern.notice] kern.notice] kern.notice] kern.notice] kern.notice]

NOTICE: NOTICE: NOTICE: NOTICE: NOTICE:

Inside Inside Inside Inside Inside

dummy_getinfo dummy_prop_op dummy_getinfo dummy_read dummy_close

Test writing to the device:


# echo hello > ls /devices/pseudo/dummy*

You should see the following messages in the window where you are viewing /var/adm/messages:
date date date date date date date date time time time time time time time time machine machine machine machine machine machine machine machine dummy: dummy: dummy: dummy: dummy: dummy: dummy: dummy: [ID [ID [ID [ID [ID [ID [ID [ID 136952 623947 891851 623947 891851 623947 672780 550206 kern.notice] kern.notice] kern.notice] kern.notice] kern.notice] kern.notice] kern.notice] kern.notice] NOTICE: NOTICE: NOTICE: NOTICE: NOTICE: NOTICE: NOTICE: NOTICE: Inside Inside Inside Inside Inside Inside Inside Inside dummy_open dummy_getinfo dummy_prop_op dummy_getinfo dummy_prop_op dummy_getinfo dummy_write dummy_close

As you can see, this output from the write test is almost identical to the output you saw from the read test. The only difference is in the seventh line of the output. Using the cat(1) command causes the kernel to access the read(9E) entry point of the driver. Using the echo(1) command causes the kernel to access the write(9E) entry point of the driver. The text argument that you give to echo(1) is ignored because this driver does not do anything with that data.

Removing the Template Driver


Make sure you are user root when you unload the driver. Use the rem_drv(1M) command to unload the driver and remove the device from the /devices directory:
# rem_drv dummy

You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 513080 kern.notice] NOTICE: Inside _info date time machine dummy: [ID 617648 kern.notice] NOTICE: Inside dummy_detach date time machine dummy: [ID 812373 kern.notice] NOTICE: Inside _fini

The dummy device is no longer in the /devices directory:


# ls /devices/pseudo/dummy* /devices/pseudo/dummy*: No such file or directory

124

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Testing the Template Driver

The next time you want to read from or write to the dummy device, you must load the driver again using add_drv(1M). You can use the modunload(1M) command to unload the driver but not remove the device from /devices. Then the next time you read from or write to the dummy device, the driver is automatically loaded. Press Control-C to stop tailing the /var/adm/messages messages.

Dummy Driver Source


The following code is the complete source for the dummy driver described in this module:
/* * Minimalist pseudo-device. * Writes a message whenever a routine is entered. * * Build the driver: * cc -D_KERNEL -c dummy.c * ld -r -o dummy dummy.o * Copy the driver and the configuration file to /usr/kernel/drv: * cp dummy.conf /usr/kernel/drv * cp dummy /tmp * ln -s /tmp/dummy /usr/kernel/drv/dummy * Add the driver: * add_drv dummy * Test (1) read from driver (2) write to driver: * cat /devices/pseudo/dummy@* * echo hello > ls /devices/pseudo/dummy@* * Verify the tests in another window: * tail -f /var/adm/messages * Remove the driver: * rem_drv dummy */ #include <sys/devops.h> /* used by dev_ops */ #include <sys/conf.h> /* used by dev_ops and cb_ops */ #include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */ /* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */

Module 12 Writing a Template Character Device Driver

125

Testing the Template Driver

#include #include #include #include #include

/* /* /* /* /* /* /* #include <sys/sunddi.h> /* /* /*

<sys/cred.h> <sys/uio.h> <sys/stat.h> <sys/cmn_err.h> <sys/ddi.h>

used by open, close, read */ used by read */ defines S_IFCHR used by ddi_create_minor_node */ used by all entry points for this driver */ used by all entry points for this driver */ also used by cb_ops, ddi_get_instance, and */ ddi_prop_op */ used by all entry points for this driver */ also used by cb_ops, ddi_create_minor_node, */ ddi_get_instance, and ddi_prop_op */

static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp); static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred); static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred); static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp); static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp); /* cb_ops structure */ static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ };

126

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Testing the Template Driver

/* dev_ops structure */ static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, nulldev, /* no identify - nulldev returns 0 */ nulldev, /* no probe */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev /* no power */ }; /* modldrv structure */ static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; /* modlinkage structure */ static struct modlinkage ml = { MODREV_1, &md, NULL }; /* dev_info structure */ dev_info_t *dummy_dip; /* keep track of one instance */

/* Loadable module configuration entry points */ int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); } int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info");

Module 12 Writing a Template Character Device Driver

127

Testing the Template Driver

return(mod_info(&ml, modinfop)); } int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); } /* Device configuration entry points */ static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH: dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } }

128

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Testing the Template Driver

static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } } /* Main entry points */ static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); } static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; } static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; } static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read");

Module 12 Writing a Template Character Device Driver

129

Testing the Template Driver

return DDI_SUCCESS; } static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }

130

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

13
M O D U L E

1 3

Debugging Drivers With DTrace

Objectives
The objective of this module is to learn about how you can use DTrace to debug your driver development projects by reviewing a case study.

131

Porting the smbfs Driver from Linux to the Solaris OS

Porting the smbfs Driver from Linux to the Solaris OS


This case study focuses on leveraging the DTrace capability for device driver development. Historically, debugging a device driver required that a developer use function calls like cmn_err() to log diagnostic information to the /var/adm/messages le. This cumbersome process requires guesswork, re-compilation, and system reboots to uncover software coding errors. Developers with a talent for assembly language can use adb and create custom modules in C for mdb to diagnose software errors. However, historical approaches to kernel development and debugging are quite time-consuming. DTrace provides a diagnostic short-cut. Instead of sifting through the /var/adm/messages le or pages of truss output, DTrace can be used to capture information on only the events that you as a developer wish to view. The magnitude of the benet provided by DTrace can best be provided through a few simple examples. First, create an smbfs driver template based on Suns nfs driver. After the driver compiles successfully, test that the driver can be loaded and unloaded successfully. First copy the prototype driver to /usr/kernel/fs and attempt to modload it by hand:
# modload /usr/kernel/fs/smbfs cant load module: Out of memory or no room in system tables

And the /var/adm/messages le contains:


genunix: [ID 104096 kern.warning] WARNING: system call missing from bind file

Searching for the system call missing message, reveals it is in the function mod_getsysent() in the le modconf.c, on a failed call to mod_getsysnum. Instead of manually searching the ow of mod_getsysnum() from source le to source le, heres a simple DTrace script to enable all entry and return events in the fbt (Function Boundary Tracing) provider once mod_getsynum() is entered.
#!/usr/sbin/dtrace -s #pragma D option flowindent fbt::mod_getsysnum:entry /execname == "modload"/ { self->follow = 1; } fbt::mod_getsysnum:return {

132

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Porting the smbfs Driver from Linux to the Solaris OS

self->follow = 0; trace(arg1); } fbt:::entry /self->follow/ { } fbt:::return /self->follow/ { trace(arg1); }


Note trace(arg1) displays the functions return value.

Executing this script and running the modload command in another window produces the following output:
# ./mod_getsysnum.d dtrace: script ./mod_getsysnum.d matched 35750 probes

CPU FUNCTION 0 -> mod_getsysnum 0 -> find_mbind 0 -> nm_hash 0 <- nm_hash 0 -> strcmp 0 <- strcmp 0 -> strcmp 0 <- strcmp 0 <- find_mbind 0 <- mod_getsysnum

41 4294967295 7 0 4294967295

Thus either find_mbind() returning 0, or nm_hash() returning 41 is the culprit. A quick look at find_mbind() reveals that a return value of 0 indicates an error state. Viewing the source to find_mbind() in /usr/src/uts/common/os/modsubr.c, reveals that were searching for a char string in a hash table. Lets use DTrace to display the contents of the search string and hash table. To view the contents of the search string we add a strcmp() trace to our previous mod_getsysnum.d script:
Module 13 Debugging Drivers With DTrace 133

Porting the smbfs Driver from Linux to the Solaris OS

fbt::strcmp:entry { printf("name:%s, hash:%s", stringof(arg0), stringof(arg1)); }

Here are the results of our next attempt to load our driver:
# ./mod_getsysnum.d dtrace: script ./mod_getsysnum.d matched 35751 probes CPU FUNCTION 0 -> mod_getsysnum 0 -> find_mbind 0 -> nm_hash 0 <- nm_hash 41 0 -> strcmp 0 | strcmp:entry name:smbfs, hash:timer_getoverrun 0 <- strcmp 4294967295 0 -> strcmp 0 | strcmp:entry name:smbfs, hash:lwp_sema_post 0 <- strcmp 7 0 <- find_mbind 0 0 <- mod_getsysnum 4294967295

So were looking for smbfs in a hash table, and its not present. How does smbfs get into this hash table? Lets return to find_mbind() and observe that the hash table variable sb_hashtab is passed to the failing nm_hash() function. A quick search of the source code reveals that sb_hashtab is initialized with a call to read_binding_file(), which takes as its arguments a config le, the hash table, and a function pointer. A few more clicks on our source code browser reveal the contents of the config le to be dened as /etc/name_to_sysnum in the le /usr/src/uts/common/os/modctl.c. It looks like we forgot to include a conguration entry for my driver. Add the following to the /etc/name_to_sysnum le and reboot.
smbfs 177 (read_binding_file() is read once at boot time.)

After rebooting the driver can be loaded successfully.


# modload /usr/kernel/fs/smbfs

Verify that the driver is loaded with the modinfo command:


134 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Porting the smbfs Driver from Linux to the Solaris OS

# modinfo | grep 160 feb21a58 160 feb21a58 160 feb21a58 160 feb21a58

smbfs 351ac 351ac 351ac 351ac

177 24 25 26

1 1 1 1

smbfs smbfs smbfs smbfs

(SMBFS syscall, client, and comm) (network filesystem) (network filesystem version 2) (network filesystem version 3)

Note Remember that this driver was based on an nfs template, which explains this output.

Lets make sure we can also unload the module:


# modunload -i 160 cant unload the module: Device busy

This is most likely due to an EBUSY errno return value. But now, since the smbfs driver is a loaded module, we have access to all of the smbfs functions:
# dtrace -l fbt:smbfs:: | wc -l 1002

This is amazing! Without any special coding, we now have access to 1002 entry and return events contained in the driver. These 1002 function handles allow us to debug my work without a special instrumented code version of the driver! Lets monitor all smbfs calls when modunload is called, using this simple DTrace script:
#!/usr/sbin/dtrace -s #pragma D option flowindent fbt:smbfs::entry { } fbt:smbfs::return { trace(arg1); }

It seems that the smbfs code is not being accessed by modunload. So, lets use DTrace to look at modunload with this script:
#!/usr/sbin/dtrace -s #pragma D option flowindent fbt::modunload:entry

Module 13 Debugging Drivers With DTrace

135

Porting the smbfs Driver from Linux to the Solaris OS

{ self->follow = 1; trace(execname); trace(arg0); } fbt::modunload:return { self->follow = 0; trace(arg1); } fbt:::entry /self->follow/ { } fbt:::return /self->follow/ { trace(arg1); }

Heres the output of this script: # ./modunload.d dtrace: script ./modunload.d matched 36695 probes CPU FUNCTION 0 -> modunload modunload 160 0 | modunload:entry 0 -> mod_hold_by_id 0 -> mod_circdep 0 <- mod_circdep 0 0 -> mod_hold_by_modctl 0 <- mod_hold_by_modctl 0 0 <- mod_hold_by_id 3602566648 0 -> moduninstall 0 <- moduninstall 16 0 -> mod_release_mod 0 -> mod_release 0 <- mod_release 3602566648 0 <- mod_release_mod 3602566648 0 <- modunload 16

136

Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

Porting the smbfs Driver from Linux to the Solaris OS

Observe that the EBUSY return value 16 is coming from moduninstall. Lets take a look at the source code for moduninstall. moduninstall returns EBUSY in a few locations, so lets look at the following possibilities: 1. if (mp->mod_prim || mp->mod_ref || mp->mod_nenabled != 0) return (EBUSY); 2. if ( detach_driver(mp->mod_modname) != 0 ) return (EBUSY); 3. if ( kobj_lookup(mp->mod_mp, "_fini") == NULL ) 4. A failed call to smbfs _fini() routine We cant directly access all of these possibilities, but lets approach them from a process of elimination. Well use the following script to display the contents of the various structures and return values in moduninstall:
#!/usr/sbin/dtrace -s #pragma D option flowindent fbt::moduninstall:entry { self->follow = 1; printf("mod_prim:%d\n", ((struct modctl *)arg0)->mod_prim); printf("mod_ref:%d\n", ((struct modctl *)arg0)->mod_ref); printf("mod_nenabled:%d\n", ((struct modctl *)arg0)->mod_nenabled); printf("mod_loadflags:%d\n", ((struct modctl *)arg0)->mod_loadflags); } fbt::moduninstall:return { self->follow = 0; trace(arg1); } fbt::kobj_lookup:entry /self->follow/ { } fbt::kobj_lookup:return /self->follow/ { trace(arg1);

Module 13 Debugging Drivers With DTrace

137

Porting the smbfs Driver from Linux to the Solaris OS

} fbt::detach_driver:entry /self->follow/ { } fbt::detach_driver:return /self->follow/ { trace(arg1); }

This script produces the following output: # ./moduninstall.d dtrace: script ./moduninstall.d matched 6 probes CPU FUNCTION 0 -> moduninstall mod_prim:0 mod_ref:0 mod_nenabled:0 mod_loadflags:1 0 -> detach_driver 0 <- detach_driver 0 -> kobj_lookup 0 <- kobj_lookup 0 <- moduninstall

0 4273103456 16

Comparing this output to the code tells us that the failure is not due to the mp structure values or the return values from detach_driver() of kobj_lookup(). Thus, by a process of elimination, it must be the status returned via the status = (*func)(); call, which calls the smbfs _fini() routine. And heres what the smbfs _fini() routine contains:
int _fini(void) { /* dont allow module to be unloaded */ return (EBUSY); }

Changing the return value to 0 and recompiling the code results in a driver that we can now load and unload, thus we have completed the objectives of this exercise. Weve used the Function Boundary Tracing provider exclusively in these examples. Note that fbt is only one of DTraces many providers.
138 Introduction to Operating Systems: A Hands-On Approach Using the OpenSolaris Project March, 2006

You might also like