jupiters 发表于 2009-03-25 22:51

如何在kernel里调用应用层函数


               
       
       
       
       
       
       
1. 简介
一般来说,应用层程序的执行是依赖与内核提供的接口,比如内核系统调用来获得系统的时间片,从而获得执行。如下是在Solaris里面的进程模型,我想在Linux里面应该也是差不多的。
http://sunsite.uakom.sk/sunworldonline/swol-09-1998/images/swol-09-insidesolaris.gif
       
       
       
       
       
       
但是,能否反其道而行之呢?也就是说,能不能从内核里面的模块直接调用应用层的函数,并且得到执行呢?我们都知道,在应用层编程有很多好处,比如,可以利用现成的代码,有好的调试工具,等等很多。如果可以把一部分程序从内核空间移到用户空间,应该对开发有很大方便。
       
       
       
       
       
       
2. 原理
我编写了一个实验程序,可以从内核空间调用应用层的程序,该程序在最新的Solaris系统实验过。具体方法是利用Solaris提供的door(RPC调用)来完成。我们都知道door是Solaris首创并且进入POSIX标准,可以用于在用户空间内不同进程间的通信。主要结构就是一个Server端用door_create建立一个服务程序,并且和一个文件进行绑定(fattach),这个程序就叫门(door)文件。客户端程序可以通过这个门文件来请求另外一个进程的服务(通过door_call)。
上述流程只是在用户空间内使用。但是除了用户空间,Solaris还提供了一套在kernel空间使用的函数。它们是
       
       
       
       
               
                        步骤
               
               
                        Server端(User
                        Layer)
               
               
                        Client端(Kernel
                        Layer)
               
       
       
               
                        1
               
               
                        door_create
               
               
                       
                       
               
       
       
               
                        2
               
               
                        fattach
               
               
                       
                       
               
       
       
               
                        3
               
               
                       
                       
               
               
                        door_kl_call
               
       
       
               
                        4
               
               
                        door_retur
               
               
                       
                       
               
       
       
       
       
       
       
       
3. 扩展
       
       
       
       
       
       
[*]利用door,很容易在用户空间内写一个daemon进程,用于向内核模块提供服务,在用户空间完成一些操作。这个daemon中最直接的可以应用在log功能上,也就是说,我们可以利用这个daemon给内核模块提供接口,将内核空间的一些信息直接dump出来,写到我们自己的log里面去。当然,还有其他功能可以扩展,就看设计者想用它干什么了!
                [*]同样,我们可以把这个体系反过来理解。在内核里面提供一个服务,在用户空间的程序通过door_call来直接调用内核的程序,在内核里面完成相应的功能。作者现在没有编写相应的程序来印证这一点,但是,这应该很容易。就是把上面的程序结构反过来应用就可以了。
       
4. 具体程序
       
       
       
       
       
       
下面的程序在最新的Solaris下面运行过。因为不清楚Linux内核是否提供了相应的功能,没有在Linux平台上实验。具体程序请见下面。值得注意的是,这个程序只是一个示例程序,它只是证明了从内核空间是可以调用用户空间函数的,并没有特别的意义。但是它是一个可以扩展的架构,我们可以在这个体系下充分的完成其他功能。
运行示例:
编译下面的两个程序,在应用层空间的生成apptool的文件,同时生成Solaris的kernel module--testmod.
# ./apptool testmod # modload ./testmod
这时,可以看到,在apptool所在的tty上打印了
"We provide service!!",这证明了我的应用层服务程序已经被刚才的testmod在内核空间里面调用了! Great!!!
   具体程序如下:
//应用层程序apptool.c
#include ....
#define    SYMLINK_DIR    "/var/run"
#define    DOORPATH_FMT    "/var/run/%s.upcall" //门文件
//app给kernel提供的服务类型,目前只写了一个服务函数
typedef enum kmod_opcode {
    /* Service calls type*/
    KMOD_OP,
      ......
} kmod_opcode_t;
/* make sure cleanup 完成了*/
#pragma fini(local_cleanup)
static int    error(char *format, ...);
static int    create_call_door();
static void    call_handler(void *cookie, char *argp, size_t arg_size,
    door_desc_t *dp, uint_t n_desc);
static void    handle_op(char *argp, size_t arg_size);
static int    make_symlink(const char *mod, const char *moddir_suffix,
    char *fullbuf, char *symlinkbuf);
static void    local_cleanup();
static char    kmodpath = "";
static char    symlinkpath = "";
#ifdef MODDIR_SUFFIX_64
//64bit的symbolink实现
static char    kmodpath64 = "";
static char    symlinkpath64 = "";
#endif
//门调用文件
static char    *upcall_file = NULL;
//门调用句柄
static int    upcall_dfd = -1;
static int    kmod_id = -1;
static mutex_t    kmod_init_mutex = DEFAULTMUTEX;
static cond_t    kmod_init_cv = DEFAULTCV;
static int    kmod_init = 0;
//主函数等待
void main_server_provider() {
    /* simple service provider, loop and pause */
    for ( ; ; )
      pause();
}
//创建一个符号连接
int make_symlink(const char *mod, const char *moddir_suffix,
    char *fullbuf, char *symlinkbuf)
{
    char    temp;
    char    *modname, *lastslash;
    int    pid_ndigits;
    int    symlink_len, name_len;
    char    dummy;
    if (moddir_suffix == NULL)
      moddir_suffix = "";
    //得到 mod path string
    if (lastslash = strrchr(mod, '/')) {
      strlcpy(temp, mod, (lastslash - mod + 2));
      strcat(temp, moddir_suffix);
      strcat(temp, lastslash + 1);
    } else {
      sprintf(temp, "%s%s", moddir_suffix, mod);
    }
    //对symbolink的名字是装载的module
    if (realpath(temp, fullbuf) == NULL) {
      error("ERROR: make_symlink: realpath(%s, fullbuf): %s\n",
            temp, strerror(errno));
      return (-1);
    }
    modname = strrchr(fullbuf, '/') + 1;
    //计算PID长度
    pid_ndigits = snprintf(dummy, sizeof (dummy), "%d", getpid());
   
      //在SYMLINK_DIR里面创建符号连接
    name_len = (MODMAXNAMELEN - 1) - (pid_ndigits + 1);
    symlink_len = sprintf(symlinkbuf, "%s/%s%.*s.%d",
      SYMLINK_DIR, moddir_suffix, name_len, modname, getpid());
    if (symlink_len >= MOD_MAXPATH) {
      error("ERROR: make_symlink: symlink name length (%d) >= "
            "MOD_MAXPATH (%d)\n", symlink_len, MOD_MAXPATH);
      return (-1);
    }
    sprintf(temp, "%s/%s", SYMLINK_DIR, moddir_suffix);
    if (mkdir(temp, 0755)0 && errno != EEXIST) {
      error("ERROR: make_symlink: mkdir %s: %s\n",
            temp, strerror(errno));
      return (-1);
    }
    if (symlink(fullbuf, symlinkbuf)0) {
      error("ERROR: make_symlink: symlink %s %s: %s\n",
            fullbuf, symlinkbuf, strerror(errno));
      return (-1);
    }
    return (0);
}
int main(int argc, char *argv[])
{
    if (argc2) {
      error("ERROR: wrong number of arguments\n");
      return (1);
    }
       //创建门
    if (make_symlink(argv, "", kmodpath, symlinkpath)0)
      return (1);
#ifdef MODDIR_SUFFIX_64
    if (make_symlink(argv, MODDIR_SUFFIX_64, kmodpath64,
      symlinkpath64)0)
      return (1);
#endif /* MODDIR_SUFFIX_64 */
    if (create_call_door()0)
      goto cleanup;
    //成为主service提供者
    main_server_provider();
   
    /* NOTREACHED */
cleanup:
    local_cleanup();
    return (1);
}
//清除环境
void local_cleanup()
{
    struct stat    mystat;
    int      error;
    if (upcall_dfd >= 0) {
      door_revoke(upcall_dfd);
      upcall_dfd = -1;
    }
    if (upcall_file != NULL) {
      fdetach(upcall_file);
      unlink(upcall_file);
      upcall_file = NULL;
    }
    if (lstat(symlinkpath, &mystat) == 0 && S_ISLNK(mystat.st_mode))
      unlink(symlinkpath);
#ifdef MODDIR_SUFFIX_64
    if (lstat(symlinkpath64, &mystat) == 0 && S_ISLNK(mystat.st_mode))
      unlink(symlinkpath64);
#endif /* MODDIR_SUFFIX_64 */
}
//错误处理函数
int error(char *format, ...)
{
    va_list ap;
    int    ret1 = 0, ret2 = 0;
    va_start(ap, format);
    ret2 = vfprintf(stderr, format, ap);
    va_end(ap);
    if (ret10 || ret20)
      return (-1);
    else
      return (ret2);
}
//创建service door
int create_call_door()
{
    const char    *kmodname;
    char      dummy;
    int      len_needed, fd;
    if ((upcall_dfd = door_create(call_handler, NULL, 0))0) {
      error("ERROR: create_call_door: door_create: %s\n",
            strerror(errno));
      return (-1);
    }
    if (kmodname = strrchr(symlinkpath, '/'))
      ++kmodname;
    else
      kmodname = symlinkpath;
    len_needed = snprintf(dummy, sizeof (dummy), DOORPATH_FMT, kmodname);
    upcall_file = malloc(len_needed + 1);
    if (upcall_file == NULL) {
      error("ERROR: create_call_door: malloc: %s\n",
            strerror(errno));
      return (-1);
    }
    sprintf(upcall_file, DOORPATH_FMT, kmodname);
    //创建door file
    fd = open(upcall_file, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
    if (fd0) {
      error("ERROR: create_upcall_door: %s: %s\n",
            upcall_file, strerror(errno));
      return (-1);
    }
    close(fd);
    //bind "door file" 和服务提供函数
    if (fattach(upcall_dfd, upcall_file)0) {
      error("ERROR: create_upcall_door: fattach: %s\n",
            strerror(errno));
      unlink(upcall_file);
      return (-1);
    }
    return (0);
}
//服务提供的handler.利用下面的handle_op提供
void call_handler(void *cookie, char *argp, size_t arg_size, door_desc_t *dp,
    uint_t n_desc)
{
    kmod_opcode_t    opcode;
    if (arg_size == 0 || argp == NULL) {
      error("ERROR: upcall_handler: no argument\n");
      door_return(NULL, 0, NULL, 0);
      error("ERROR: upcall_handler: door_return: %s\n",
            strerror(errno));
      return;
    }
    handle_op(argp, arg_size);
}
//真正的服务提供者
void handle_op(char *argp, size_t arg_size)
{
    //在用户空间里面的实际服务功能完成函数,在这里我只让他输出我一个字符串,
    //当然,提供的服务可以更复杂!
    printf("We provide service!!\n");
    door_return(NULL, 0, NULL, 0);
    error("ERROR: handle_op: door_return: %s\n", strerror(errno));
}
//内核模块mod.c
typedef enum kmod_opcode {
    /* Service calls type */
    KMOD_OP,
} kmod_opcode_t;
#define    DOORPATH_FMT    "/var/run/%s.upcall"
extern struct mod_ops mod_miscops;
//内核注册misc结构
static struct modlmisc modlmisc = {
    &mod_miscops,
    "mod request app tests"
};
//linkage结构
static struct modlinkage modlinkage = {
    MODREV_1,
    (void *)&modlmisc,
    NULL
};
//内核信息模块
typedef struct kmod_msg {
    kmod_opcode_t    opcode;
} kmod_msg_t;
#define    STATE_CLEANUP_CALLED      0x2
static door_handle_t    upcall_dhdl;
static int      state;
static kmutex_t      state_mutex;
//向用户空间的apptool.c请求服务
void req_service_from_client();
//内核模块初始化,
int client_init(struct modlinkage *linkagep)
{
    struct modctl    *mp;
    char      dummy;
    char      *upcall_file = NULL;
    int      buflen;
    int      error = 0;
    door_arg_t    darg;
    door_desc_t    ddesc;
    int      num_ics;
    kmod_init_t      *req = NULL;
    size_t            reqsize = 0;
    kmod_init_reply_t    reply;
    /* 得到模块名字. 因为我们利用名字来得到door file */
    if ((mp = mod_getctl(linkagep)) == NULL) {
      printf("ERROR: client_init: mod_getctl returned NULL\n");
      return (EIO);
    }
    mutex_init(&state_mutex, NULL, MUTEX_DEFAULT, NULL);
    /*
   * Figure out name of the file for door upcall based on the
   * module name.
   */
    buflen = snprintf(dummy, sizeof (dummy), DOORPATH_FMT, mp->mod_modname);
    ++buflen;
    upcall_file = kmem_alloc(buflen, KM_SLEEP);
    sprintf(upcall_file, DOORPATH_FMT, mp->mod_modname);
    cmn_err(CE_NOTE, "upcall_file is %s", upcall_file);
    //连接door
    if (error = door_ki_open(upcall_file, &upcall_dhdl)) {
      printf("ERROR: client_init: door_ki_open(%s) returned %d\n",
            upcall_file, error);
      goto out;
    }
    //请求服务
    req_service_from_client();
   
out:
      //错误处理
    if (req != NULL)
      kmem_free(req, reqsize);
    if (upcall_file != NULL)
      kmem_free(upcall_file, buflen);
    if (error) {
      if (upcall_dhdl != NULL) {
            door_ki_rele(upcall_dhdl);
            upcall_dhdl = NULL;
      }
      mutex_destroy(&state_mutex);
    }
    return (error);
}
int client_fini()
{
       //清楚door的残留信息
    door_info_t    dinfo;
    int      error;
    if (upcall_dhdl != NULL) {
      door_ki_rele(upcall_dhdl);
      upcall_dhdl = NULL;
    }
    mutex_destroy(&state_mutex);
    return (0);
}
//向apptool提出服务
void req_service_from_client()
{
    kmod_msg_t    req;
    door_arg_t    darg;
    int      error;
    //仅有的服务类型
    req.opcode = KMOD_OP;
    //door传送的数据类型
    darg.data_ptr = (char *)&req;
    darg.data_size = sizeof (req);
    darg.desc_ptr = NULL;
    darg.desc_num = 0;
    darg.rbuf = NULL;
    darg.rsize = 0;
    //调用用户空间程序
    if (error = door_ki_upcall(upcall_dhdl, &darg)) {
      printf("ERROR: req_service_from_client: door_ki_upcall "
            "returned %d\n", error);
    }
}
//内核模块注册
int _init()
{
    int          error;
    struct modctl   *mp;
    char         *modname;
    if (error = mod_install(&modlinkage)) {
      return (error);
    }
    //得到模块名
    if ((mp = mod_getctl(&modlinkage)) == NULL) {
      return (EIO);
    }
    modname = mp->mod_modname;
    //在这里请求用户空间的服务,当然,也可以在其他部分调用,只要用户空间
    //的service provider存在的话。这只是一个示例!!!
    if (error = client_init(&modlinkage)) {
      return (error);
    }
    return (0);
}
int _fini()
{
    int    error;
    if (error = client_fini()) {
      return (error);
    }
    if (error = mod_remove(&modlinkage)) {
      return (error);
    }
    return (0);
}
int
_info(struct modinfo *modinfop)
{
    return (mod_info(&modlinkage, modinfop));
}
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/412/showart_1879757.html
页: [1]
查看完整版本: 如何在kernel里调用应用层函数