UNIX domain sockety na FreeBSD

No Comments

Po delší době jsem se pokusil pracovat s UNIX sokety na FreeBSD, a opět jsem řešil stejný problém jako už dříve. To je důvod, proč jsem se pokusil vytvořit návod jak na to.

Pokud se pokusíte jít podle běžného návodu tak narazíte na problém, který se projeví krátkým jménem soketu (přesně o jeden znak). Problém je ukryt ve struktuře sockaddr_un.

Standard pro sockaddr_un je:

struct sockaddr_un {
    sa_family_t sun_family;  /* AF_UNIX */
    char sun_path[108];      /* pathname */
};

kdežto na FreeBSD (ale třeba i na OSX) je:

struct sockaddr_un {
    unsigned char sun_len;   /* length including null */
    sa_family_t sun_family;  /* AF_LOCAL */
    char sun_path[104];      /* pathname */
};

Běžný postup je zavolat unlink() před bind(), a výpočet délky viz. níže.

len = strlen(local.sun_path) + sizeof(local.sun_family);
if (bind(s, (struct sockaddr *)&local, len) == -1) {
  // Tohle nefunguje
}

Správná cesta je …

if (bind(s, (struct sockaddr *)&local, SUN_LEN(&local)) == -1) {
  perror("bind");
  exit(1);
}

Kompletní řešení může být takovéto:

/* 
 * File:   unixsock_server.c
 * Author: Tomas Jelinek <platanek at gmail.com>
 *
 * Created on June 29, 2013, 6:43 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>


#define SOCK_PATH  "/var/run/my_socket"
#define UX_BACKLOG 5

/*
 * 
 */
int main(int argc, char** argv) {

    
    struct sockaddr_un local;
    struct sockaddr_un remote;
    int ux_socket;
    
    /* 
     * domain   - PF_LOCAL    - Host-internal protocols, formerly called PF_UNIX
     * type     - SOCK_STREAM - Stream socket
     * protocol - 0           - default
     */
    if ((ux_socket = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1) {
        perror("socket"); /* TODO handle error*/
        exit(EXIT_FAILURE);
    }

    /*
     * TODO comment
     */
    if (strlen(SOCK_PATH) >= sizeof(local.sun_path)) {
        perror("path to long!"); /* TODO handle error */
        exit(1);
    }
    
        
    /*
     * Create the address we will bind to.      
     */
    local.sun_len = sizeof(local);
    local.sun_family = AF_LOCAL;
    strcpy(local.sun_path, SOCK_PATH);
    
    /*
     * We unlink the name first, so that bind won't 
     * fail
     */
    unlink(local.sun_path);
    
    
    /*
     * Try to bind the address to the socket.
     * 
     * The third argument indicates the "length" of
     * the structure, not just the length of the 
     * socket name
     */
    
    if (bind(ux_socket, (struct sockaddr *)&local, SUN_LEN(&local)) == -1) {
        perror("bind"); /* TODO  handle error*/
        exit(1);
    }
    
    
    /*
     * Listen on the socket
     */
    if (listen(ux_socket, UX_BACKLOG) == -1) {
        perror("listen"); /* TODO handle error */
        exit(1);
    }
        
    close(ux_socket);
    unlink(local.sun_path);
    
    return (EXIT_SUCCESS);
}

Tohle mne dostalo už tolikrát, tak doufám že to někomu pomůže, a ušetří nervy.

Jednoduchý kernelový modul pro FreeBSD

No Comments

Rozhodl jsem se pustit do velkého dobrodružství, a tím je vývoj ovladače pro jeden nejmenovaný distribuovaný filesystém na FreeBSD. Tedy pouze klienta. Odvážně se pouštím tam, kam se dosud nikdo nevydal…

Dobrá tedy, začněme s vytvářením kernelového modulu do FreeBSD. Tady je návod jak vytvořit jednoduchý modul, který při nahrání a odebrání z/do kernelu zapíše záznam do syslogu. Nic víc.

Události definované v module.h

První na co se podíváme, je hlavičkový soubor module.h (/usr/include/sys/module.h) Při nahrávání/odebírání modulu do kernelu se volá module event handler, který ošetřuje tyto události (dříve existovaly jen první tři, bez MOD_QUIESCE).

  • MOD_LOAD
  • MOD_UNLOAD
  • MOD_SHUTDOWN
  • MOD_QUIESCE
typedef enum modeventtype {
        MOD_LOAD,
        MOD_UNLOAD,
        MOD_SHUTDOWN,
        MOD_QUIESCE
} modeventtype_t;

typedef struct module *module_t;
typedef int (*modeventhand_t)(module_t, int /* modeventtype_t */, void *);

Když je modul nahrán, tak je event handler zavolán s argumentem what nastaveným na hodnotu MOD_LOAD.

Při odstraňování, tedy po zavolání kldunload, je event handler volán s what nastaveným na MOD_QUIESCE. Jestliže se akce nezdaří, pak je vrácena nenulová hodnota. Pakliže odstraňování pokračuje, potom je what nastaven na MOD_UNLOAD. Když modul vrátí nenulovou hodnoty, pak se odstranění z jádra neprovede. Celé je to ještě složitější, když se to provádí na více modulech naráz.

Rozdíl mezi MOD_QUIESCE a MOD_UNLOAD je v tom, že MOD_QUIESCE může skončit s chybou když je modul právě používán, ale MOD_UNLOAD skončí s chybou jen když je nemožné nemožné jej odstranit (existují odkazy na paměť, které nemohou být zrušeny).

Jestliže je vypínán systém, pak what je MOD_SHUTDOWN.

U nepodporovaných a nerozeznaných hodnot what může modul vracet EOPNOTSUPP

DECLARE_MODULE v module.h

Dalším krokem je prolinkování s kernelem. Stačí prostě zavolat DECLARE_MODULE.

#define DECLARE_MODULE(name, data, sub, order)

Macro má čtyři argumenty:

  • name
  • Jméno modulu, které se volá v SYSINIT() k identifikaci modulu.

  • data
  • Struktura moduledata_t, která má dvě položky, oficiální jméno modulu a ukzatel na fuknci obsluhující události (event handler).

  • sub
  • Odkazuje na SYSINIT() macro, a určuje typ systémových rozhraní. Platné hodnoty obsahuje sysinit_sub_id v . Např. SI_SUB_DRIVERS se použije pro ovladač.

  • order
  • Opet pro SYSINIT(). Určuje pořadí v jakém bude modul nahrán. Platné hodnoty jsou SI_ORDER_FIRST, SI_ORDER_SECOND, SI_ORDER_THIRD, SI_ORDER_FOURTH, SI_ORDER_MIDDLE, SI_ORDER_ANY.

Pro úplnost přikládám i celý soubor module.h, může se ještě hodit. :-)

/*-
 * Copyright (c) 1997 Doug Rabson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/sys/sys/module.h,v 1.25.2.2.2.1 2010/12/21 17:09:25 kensmith Exp $
 */

#ifndef _SYS_MODULE_H_
#define _SYS_MODULE_H_

/*
 * Module metadata types
 */
#define MDT_DEPEND      1               /* argument is a module name */
#define MDT_MODULE      2               /* module declaration */
#define MDT_VERSION     3               /* module version(s) */

#define MDT_STRUCT_VERSION      1       /* version of metadata structure */
#define MDT_SETNAME     "modmetadata_set"

typedef enum modeventtype {
        MOD_LOAD,
        MOD_UNLOAD,
        MOD_SHUTDOWN,
        MOD_QUIESCE
} modeventtype_t;

typedef struct module *module_t;
typedef int (*modeventhand_t)(module_t, int /* modeventtype_t */, void *);

/*
 * Struct for registering modules statically via SYSINIT.
 */
typedef struct moduledata {
        const char      *name;          /* module name */
        modeventhand_t  evhand;         /* event handler */
        void            *priv;          /* extra data */
} moduledata_t;

/*
 * A module can use this to report module specific data to the user via
 * kldstat(2).
 */
typedef union modspecific {
        int     intval;
        u_int   uintval;
        long    longval;
        u_long  ulongval;
} modspecific_t;

/*
 * Module dependency declarartion
 */
struct mod_depend {
        int     md_ver_minimum;
        int     md_ver_preferred;
        int     md_ver_maximum;
};

/*
 * Module version declaration
 */
struct mod_version {
        int     mv_version;
};

struct mod_metadata {
        int             md_version;     /* structure version MDTV_* */
        int             md_type;        /* type of entry MDT_* */
        void            *md_data;       /* specific data */
        const char      *md_cval;       /* common string label */
};

#ifdef  _KERNEL

#include <sys/linker_set.h>

#define MODULE_METADATA(uniquifier, type, data, cval)                   \
        static struct mod_metadata _mod_metadata##uniquifier = {        \
                MDT_STRUCT_VERSION,                                     \
                type,                                                   \
                data,                                                   \
                cval                                                    \
        };                                                              \
        DATA_SET(modmetadata_set, _mod_metadata##uniquifier)

#define MODULE_DEPEND(module, mdepend, vmin, vpref, vmax)               \
        static struct mod_depend _##module##_depend_on_##mdepend = {    \
                vmin,                                                   \
                vpref,                                                  \
                vmax                                                    \
        };                                                              \
        MODULE_METADATA(_md_##module##_on_##mdepend, MDT_DEPEND,        \
            &_##module##_depend_on_##mdepend, #mdepend)

/*
 * Every kernel has a 'kernel' module with the version set to
 * __FreeBSD_version.  We embed a MODULE_DEPEND() inside every module
 * that depends on the 'kernel' module.  It uses the current value of
 * __FreeBSD_version as the minimum and preferred versions.  For the
 * maximum version it rounds the version up to the end of its branch
 * (i.e. M99999 for M.x).  This allows a module built on M.x to work
 * on M.y systems where y >= x, but fail on M.z systems where z < x.
 */
#define MODULE_KERNEL_MAXVER    (roundup(__FreeBSD_version, 100000) - 1)

#define DECLARE_MODULE_WITH_MAXVER(name, data, sub, order, maxver)      \
        MODULE_DEPEND(name, kernel, __FreeBSD_version,                  \
            __FreeBSD_version, maxver);                 \
        MODULE_METADATA(_md_##name, MDT_MODULE, &data, #name);          \
        SYSINIT(name##module, sub, order, module_register_init, &data); \
        struct __hack

#define DECLARE_MODULE(name, data, sub, order)                          \
        DECLARE_MODULE_WITH_MAXVER(name, data, sub, order, MODULE_KERNEL_MAXVER)

/*
 * The module declared with DECLARE_MODULE_TIED can only be loaded
 * into the kernel with exactly the same __FreeBSD_version.
 *
 * Use it for modules that use kernel interfaces that are not stable
 * even on STABLE/X branches.
 */
#define DECLARE_MODULE_TIED(name, data, sub, order)                             \
        DECLARE_MODULE_WITH_MAXVER(name, data, sub, order, __FreeBSD_version)

#define MODULE_VERSION(module, version)                                 \
        static struct mod_version _##module##_version = {               \
                version                                                 \
        };                                                              \
        MODULE_METADATA(_##module##_version, MDT_VERSION,               \
            &_##module##_version, #module)

extern struct sx modules_sx;

#define MOD_XLOCK       sx_xlock(&modules_sx)
#define MOD_SLOCK       sx_slock(&modules_sx)
#define MOD_XUNLOCK     sx_xunlock(&modules_sx)
#define MOD_SUNLOCK     sx_sunlock(&modules_sx)
#define MOD_LOCK_ASSERT sx_assert(&modules_sx, SX_LOCKED)
#define MOD_XLOCK_ASSERT        sx_assert(&modules_sx, SX_XLOCKED)

struct linker_file;

void    module_register_init(const void *);
int     module_register(const struct moduledata *, struct linker_file *);
module_t        module_lookupbyname(const char *);
module_t        module_lookupbyid(int);
int     module_quiesce(module_t);
void    module_reference(module_t);
void    module_release(module_t);
int     module_unload(module_t);
int     module_getid(module_t);
module_t        module_getfnext(module_t);
const char *    module_getname(module_t);
void    module_setspecific(module_t, modspecific_t *);
struct linker_file *module_file(module_t);

#ifdef  MOD_DEBUG
extern int mod_debug;
#define MOD_DEBUG_REFS  1

#define MOD_DPF(cat, args) do {                                         \
        if (mod_debug & MOD_DEBUG_##cat)                                \
                printf(args);                                           \
} while (0)

#else   /* !MOD_DEBUG */

#define MOD_DPF(cat, args)
#endif
#endif  /* _KERNEL */

#define MAXMODNAME      32

struct module_stat {
        int             version;        /* set to sizeof(struct module_stat) */
        char            name[MAXMODNAME];
        int             refs;
        int             id;
        modspecific_t   data;
};

#ifndef _KERNEL

#include <sys/cdefs.h>

__BEGIN_DECLS
int     modnext(int _modid);
int     modfnext(int _modid);
int     modstat(int _modid, struct module_stat *_stat);
int     modfind(const char *_name);
__END_DECLS

#endif

#endif  /* !_SYS_MODULE_H_ */

Jadený modul

Pojďme využít předchozích znalostí k vytvoření primitivního modulu, a trochu oprášíme znalost jazyka ANSI C. Následující zdrojový soubor jsem vytvořil ve zvláštním adresáři, třeba /root/KIVFS, a pojmenoval kivfs-FreeBSD-client.c

#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/syslog.h>

/* 
 * Load handler that deals with the loading and unloading of a KLD.
 */
static int load_module(struct module *m, int event, void *arg)
{
    int error = 0;
    switch (event)
    {
        case MOD_LOAD:                            /* kldload */
            log(LOG_INFO, "KIVFS module loaded\n");
            break;

        case MOD_QUIESCE:                         /* kldunload */
            log(LOG_INFO, "KIVFS module will be unloaded\n");
            break;

        case MOD_UNLOAD:                          /* kldunload */
            log(LOG_INFO, "KIVFS module unloaded\n");
            break;

        default:
            error = EOPNOTSUPP;
            break;
    }
    return(error);
}

static moduledata_t kivfs_module =
{
    "kivfs",
    load_module,
    NULL
};

DECLARE_MODULE(kivfs, kivfs_module, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

A k němu odpovídající Makefile. KMOD je jméno modulu, a SRCS je jméno zdrojového souboru.

KMOD= kivfs
SRCS= kivfs-freebsd-client.c
.include <bsd.kmod.mk>

Ted v našem adresáři spustíme příkaz make, a bude hotovo. No není to jednoduché?

# make
Warning: Object directory not changed from original /root/KIVFS
@ -> /usr/src/sys
machine -> /usr/src/sys/amd64/include
cc -O2 -pipe -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -I. -I@ -I@/contrib/altq -I@/../include -I/usr/include -finline-limit=8000 -fno-common -mno-align-long-strings -mpreferred-stack-boundary=2 -mno-mmx -mno-3dnow -mno-sse -mno-sse2 -mno-sse3 -ffreestanding -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -fformat-extensions -std=c99 -c kld.c
ld -d -warn-common -r -d -o kivfs.ko kivfs-freebsd-client.o
:> export_syms
awk -f /sys/conf/kmod_syms.awk kivfs.ko export_syms | xargs -J% objcopy % kivfs.ko
objcopy --strip-debug kivfs.ko
#

Pokud nedojde k nějaké chybě, která nastat prostě nemůže :), tak už stačí jen modul nahrát, a podívat se do syslogu.

# kldload  ./kivfs.ko
# kldunload ./kivfs.ko
# tail -3 /var/log/messages
Nov 27 21:35:29  kernel: KIVFS module loaded
Nov 27 21:35:33  kernel: KIVFS module will be unloaded
Nov 27 21:35:33  kernel: KIVFS module unloaded
#

Závěr

Dál už tady mám jenom nožičky a závěr. K napsání tohoto článku mne vedla snaha udělat si v dané problematice jasno. Řekl bych, že to pomohlo. Snad to pomůže i někomu dalšímu.

jaderné moduly ve FreeBSD

No Comments

Ve FreeBSD se jaderné moduly nazívají KLD, a ovládají se těmito příkazy:

  • kldload – nahraje kernelový modul
  • kldunload – odstraní z kernelu modul
  • kldstat – vypíše aktuálně nahrané moduly

Moduly se běžně nahrávají z těchto adresářů /boot/kernel a /boot/modules, případně z cesty kterou nadefinujeme pomocí kldconfig. Aktuálně platné adresáře vypíše příkaz kldconfig -r.

Pro nahrání modulu je tedy potřeba nakopírovat jej do správného adresáře, nebo použít absolutní/relativní cestu k souboru modulu (koncovka .ko).

# kldload -v kivfs
Loaded kivfs, id=2
#
# kldstat
Id Refs Address            Size     Name
 1    3 0xffffffff80100000 c9fe20   kernel
 2    1 0xffffffff80e22000 13a      kivfs.ko
#
# kldunload kivfs
# tail -2 /var/log/messages
Nov 26 21:18:16  kernel: KIVFS loaded
Nov 26 21:22:33  kernel: KIVFS unloaded
#
# kldstat
Id Refs Address            Size     Name
 1    3 0xffffffff80100000 c9fe20   kernel
#

powered by EndomondoWP