One more reason we must have iPad

Make a VM template without using LVM in KVM

In my opinion, template is the most useful feature in virtualization technology. After creating one fully installed VM, you can create several temporary VMs from the original without spend lots of hours or lots of disk space. In LVM environment, you can use ’snapshot’ feature of lvcreate command. But, LVM volume is not easy to move between systems especially when you want to upgrade to the latest distribution just like Fedora.

If we can create new VMs from the file-based image, it could be very convenient to migrate between systems and easy to back up. With qemu-img utility you can do the same thing as LVM do.

Following are the steps I did to create new VM from the old one which is file-based.

1
2
3
$ qemu-img convert rhel4u5.img -O qcow2 rhel4u5.qcow2
$ qemu-img create -b rhel4u5.qcow2 -f qcow2 rhel4u5-test1.qcow2
$ virt-install -nrhel4u5-test1 -r512 -v --os-type=linux --os-variant=rhel4 --import --disk path=rhel4u5-test1.qcow2

Easy, isn’t it?
The first step is only necessary when your original VM was created with raw format.

What’s the internal details for ‘-startdate’ option in ‘qemu-kvm’

When you run KVM-based virtual machine, you can specify ‘-startdate’ option. What format we can use and what actions are happening inside the qemu-kvm.

Options are checked in main() function in vl.c.

1
2
3
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
29
30
31
32
33
34
35
36
37
38
            case QEMU_OPTION_startdate:
                {
                    struct tm tm;
                    time_t rtc_start_date;
                    if (!strcmp(optarg, "now")) {
                        rtc_date_offset = -1;
                    } else {
                        if (sscanf(optarg, "%d-%d-%dT%d:%d:%d",
                               &tm.tm_year,
                               &tm.tm_mon,
                               &tm.tm_mday,
                               &tm.tm_hour,
                               &tm.tm_min,
                               &tm.tm_sec) == 6) {
                            /* OK */
                        } else if (sscanf(optarg, "%d-%d-%d",
                                          &tm.tm_year,
                                          &tm.tm_mon,
                                          &tm.tm_mday) == 3) {
                            tm.tm_hour = 0;
                            tm.tm_min = 0;
                            tm.tm_sec = 0;
                        } else {
                            goto date_fail;
                        }
                        tm.tm_year -= 1900;
                        tm.tm_mon--;
                        rtc_start_date = mktimegm(&tm);
                        if (rtc_start_date == -1) {
                        date_fail:
                            fprintf(stderr, "Invalid date format. Valid format are:\n"
                                    "'now' or '2006-06-17T16:01:21' or '2006-06-17'\n");
                            exit(1);
                        }
                        rtc_date_offset = time(NULL) - rtc_start_date;
                    }
                }
                break;

rtc_date_offset is declared as a global variable in vl.c.

1
static int rtc_date_offset = -1; /* -1 means no change */

This variable is used to use time information in virtual machine as shown below.

1
2
3
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
29
30
31
32
33
34
35
36
/***********************************************************/
/* host time/date access */
void qemu_get_timedate(struct tm *tm, int offset)
{
    time_t ti;
    struct tm *ret;
 
    time(&ti);
    ti += offset;
    if (rtc_date_offset == -1) {
        if (rtc_utc)
            ret = gmtime(&ti);
        else
            ret = localtime(&ti);
    } else {
        ti -= rtc_date_offset;
        ret = gmtime(&ti);
    }
 
    memcpy(tm, ret, sizeof(struct tm));
}
 
int qemu_timedate_diff(struct tm *tm)
{
    time_t seconds;
 
    if (rtc_date_offset == -1)
        if (rtc_utc)
            seconds = mktimegm(tm);
        else
            seconds = mktime(tm);
    else
        seconds = mktimegm(tm) + rtc_date_offset;
 
    return seconds - time(NULL);
}

In here, we can see that the time information is basically come from system OS’s time facilities such as gmtime and localtime. This information can be modified if customer wants to.

How could I reduce kernel’s binary size as small as distribution’s size

The basic steps to compile kernel and modules are similar to following.

1
make modules && make bzImage && make modules_install && make install

It will install the kernel related files into the proper location after compile modules and kernel. But maybe you could be surprised by the final binaries size. Sometimes, it shows more than 10 times bigger size than distributor’s module and kernel size. It even happened with distribution’s configuration.

I will show some basic tips you can use to build your own kernel from distribution’s source files without any increase in size.

First, you should download source files from distributor’s site or get it from distribution media. In my case, I downloaded the kernel rpm from Red Hat Network (http://rhn.redhat.com).

After installing source rpms and you need to rebuild source codes from the spec.

1
2
3
4
$ rpm -ivh kernel-2.6.18-128.1.1.el5.src.rpm
$ cd /usr/src/redhat/SPECS
$ rpmbuild -bp --target=i686 kernel-2.6.spec
$ cd /usr/src/redhat/BUILD/kernel-2.6.18/linux-2.6.18.x86_64/

You can see the patched kernel codes in BUILD directory. After doing some modification for your purpose, it is time to rebuild kernel. To reduce kernel size, add ‘INSTALL_MOD_STRIP=1′ options in make command.

1
make modules && make bzImage && make INSTALL_MOD_STRIP=1 modules_install && make INSTALL_MOD_STRIP=1 install

It will strip off unnecessary debug information from the compiled images. If you can keep another kernel images separately which has debug information for the debugging purpose.

This methods work for RHEL5 and Fedora 11 or 12 (I couldn’t check this with earlier version but would be applied for earlier version of Fedora). If you want to reduce sizes in RHEL4 or earlier version, you should strip off DEBUG_* related options from the configuration file before do compile.

Following is the part of RHEL4 spec file.

1
2
3
4
5
6
7
8
9
10
11
12
13
# set the EXTRAVERSION to <version>custom, so that people who follow a kernel building howto
# don't accidentally overwrite their currently working moduleset and hose
# their system
perl -p -i -e "s/^EXTRAVERSION.*/EXTRAVERSION = -%{release}custom/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/Makefile
 
# some config options may be appropriate for an rpm kernel build but are less so for custom user builds,
# change those to values that are more appropriate as default for people who build their own kernel.
perl -p -i -e "s/^CONFIG_DEBUG_INFO.*/# CONFIG_DEBUG_INFO is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*
perl -p -i -e "s/^.*CONFIG_DEBUG_PAGEALLOC.*/# CONFIG_DEBUG_PAGEALLOC is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*
perl -p -i -e "s/^.*CONFIG_DEBUG_SLAB.*/# CONFIG_DEBUG_SLAB is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*
perl -p -i -e "s/^.*CONFIG_DEBUG_SPINLOCK.*/# CONFIG_DEBUG_SPINLOCK is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*
perl -p -i -e "s/^.*CONFIG_DEBUG_HIGHMEM.*/# CONFIG_DEBUG_HIGHMEM is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*
perl -p -i -e "s/^.*CONFIG_MODULE_SIG.*/# CONFIG_MODULE_SIG is not set/" $RPM_BUILD_ROOT/usr/src/linux-%{KVERREL}/configs/*

As you can see, it strips off some DEBUG related options to blank.