2013年9月1日 星期日

Linux Device Driver - IOCTL (copy_to_user & copy_from_user)

在 linux/include/linux/fs.h 中定義許多file operation能用的handler
這一次我針對的是 read、ioctl做練習
file operation中沒實做到的handler會被定義成NULL
這一支程式會先得到來自kernel的回應,接著kernel 換更改指定的數字

先大概說一下主要概念
我要先寫一個mydev.c 當作 driver
執行完make 之後 就把 mydev.ko 加入到 kernel中
此時的 mydev.ko 就是模組, 一個在kernel中的模組了
那要如何與此模組互動呢?
我要再寫一個 test_mydev.c 當作 user
直接把他 gcc 產生執行檔 再指定我要跟mydev 模組溝通

首先先來看 ioc.h
這裡定義了 user 能夠透過 ioctl 傳達的指令
IOC_MAGIC 是device 的 major number , 而我定義其major number為 k
每一個裝置都有其major number 與 minor number
接著我設置 SETNUM  GETNUM  XNUM 3種指令
_IOW  _IOR  _IOWR 是 kernel 的巨集 , 我們用這3個巨集來定義我們的命令要做哪些事
各聚集裡的1,2,3 是自訂的minor number

#ifndef IOC_H
#define IOC_H

#define IOC_MAGIC 'k'
#define SETNUM _IOW(IOC_MAGIC, 1 , int)
#define GETNUM _IOR(IOC_MAGIC, 2 , int)
#define XNUM _IOWR(IOC_MAGIC,3 , int)
#define IOC_MAXNR 3

#endif


接著看到最主要的 driver code : mydev.c
在file operations 中定義了我自訂的操作函數
dev_open : 在user open  device file 時會被呼叫
dev_close : 在user close device file 時會被呼叫
dev_ioctl  :  switch case 中寫了各種command該執行的動作
         若user 呼叫 ioctl 時傳入 SETNUM 則 driver 會將傳入的資料(args)取出,並指定給val 變數
         若user 呼叫 ioctl 時傳入 GETNUM 則 driver 會將val 變數傳出(args)
         若user 呼叫 ioctl 時傳入XNUM 則 driver 會將 user 輸入的數值指派給val變數達到更改資料
         的目的
         (因為user space 與 kernel space 儲存data的空間不同, 因此要用 copy_to_user 與
         copy_from_user來轉遞資料)
dev_read : user 呼叫 read時會被呼叫, 我在這裡放了一個hello字串, kernel會利用copy_to_user 傳到
                 buffer中給user
dev_write: 我在這裡沒用到
init_modules:  程式進入點 , 建立裝置並註冊到kernel
exit_modules: 程式結束 , 移除裝置

#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h> //chrdev
#include <linux/cdev.h> //cdev_add() / cdev_del()

#include <asm/uaccess.h> //copy_*_user()

#include "ioc.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("IRENE 2013/09/01");
#define DEV_BUFSIZE 1024

static int dev_major;
static int dev_minor;
struct cdev *dev_cdevp = NULL;
static int dev_open(struct inode*, struct file*);
static int dev_release(struct inode*, struct file*);
static int dev_ioctl(struct inode*, struct file*, unsigned int, unsigned long);
ssize_t dev_read(struct file*, char __user*, size_t, loff_t*);
ssize_t dev_write(struct file*, char __user*, size_t, loff_t*);
static void __exit exit_modules(void);

//Functions on the right side are the handler we just defined
struct file_operations dev_fops = {
   .owner = THIS_MODULE,
   .open = dev_open,
   .release= dev_release,
   .ioctl = dev_ioctl,
   .read = dev_read,
   .write = dev_write
};


// dev_open will be Called when user open the device file
static int dev_open(struct inode *inode, struct file *filp)
{
       printk("%s():\n",__FUNCTION__);
       return 0;
}

// dev_release will be Called when user close the device file
static int dev_release(struct inode *inode, struct file *filp)
{
       printk("%s():\n",__FUNCTION__);
       return 0;
}

// mainly io control between user and kernel
// args are pointed to user space's buffer
static int val = 0;
static int dev_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long args)
{
      int tmp, err=0,ret=0;

      if(_IOC_TYPE(cmd) != IOC_MAGIC)
              return -ENOTTY;
      if(_IOC_NR(cmd) > IOC_MAXNR)
              return -ENOTTY;
      if(_IOC_DIR(cmd) & _IOC_READ){
              err = !access_ok(VERIFY_WRITE, (void __user*)args, _IOC_SIZE(cmd));
      } else if (_IOC_DIR(cmd) & (_IOC_WRITE)){
              err = !access_ok(VERIFY_READ, (void __user *)args,_IOC_SIZE(cmd));
      }

      if (err)
              return -EFAULT;

      switch (cmd) {
        case SETNUM:
             //copy data from args(user) to val(kernel)
             if(copy_from_user(&val,(int __user *)args,1))
                 return -1;

             printk("%s():get val from user = %d\n",__FUNCTION__,val);
                   break;
        case GETNUM:
                        //copy data from val to args
              if(copy_to_user((int __user *)args,&val,1))
                    return -1;

              printk("%s():set val to %d\n",__FUNCTION__, val);
                    break;
        case XNUM:
                       // exchange data passed by user
              tmp = val;
              if(copy_from_user(&val,(int __user *)args,1))
                        return -1;

              if(copy_to_user((int __user *)args,&val,1))
                        return -1;

           printk("%s():change val from %d to %d\n",__FUNCTION__,tmp,val);
           break;

        default: /* redundant. as cmd was checked against MAXNR */
                 return -ENOTTY;
     }
     return 0;
}


static const char str[]="Hello Irene from kernel!";
static const ssize_t str_size = sizeof(str);
// send messages to user
ssize_t dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{

     printk("%s():\n",__FUNCTION__);

     if( *f_pos >= str_size)
          return 0;
     if( *f_pos + count > str_size)
        count = str_size - *f_pos;
     if (copy_to_user(buf, str + *f_pos, count))
        return -EFAULT;
     *f_pos += count;
     return count;

}

ssize_t dev_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
     printk("%s():\n",__FUNCTION__);
     return 0;
}

static int __init init_modules(void)

   dev_t dev;
   int ret;
   // get major number dynamically
   ret = alloc_chrdev_region(&dev, 0, 1, "mydev");
   if (ret <0){
      printk("can't alloc chrdev\n");
      return ret;
   }
   dev_major = MAJOR(dev);
   dev_minor = MINOR(dev);
   printk("register chrdev(%d,%d)\n",dev_major,dev_minor);

   dev_cdevp = kmalloc(sizeof(struct cdev), GFP_KERNEL);
   if (dev_cdevp == NULL){
        printk("kmalloc failed\n");
        goto failed;
   }
   // system call handler
   cdev_init(dev_cdevp, &dev_fops);
   dev_cdevp->owner = THIS_MODULE;
   //register my device to kernel
   ret = cdev_add(dev_cdevp, MKDEV(dev_major, dev_minor),1);
   if(ret < 0){
        printk("add chr dev failed\n");
        goto failed;
   }
   return 0;

failed:
   if(dev_cdevp) {
   kfree(dev_cdevp);
   dev_cdevp = NULL;
}
  return 0;
}


static void __exit exit_modules(void)
{
   dev_t dev;
   dev = MKDEV(dev_major, dev_minor);
   if (dev_cdevp){
      cdev_del(dev_cdevp);
      kfree(dev_cdevp);
    }
   unregister_chrdev_region(dev,1);
   printk("unregister chrdev\n");
}

module_init(init_modules);
module_exit(exit_modules);


接著 是 user 端部分啦 test_mydev.c
執行時要輸入的參數有 devicefile num
device file 就是 在 /dev下的裝置檔名
num 是 XNUM命令時要change的value (default 是 10 )
首先利用 open 打開 device file
然後 read(fd, buffer, sizeof(buffer));
buffer 是 user 拿來放kernel 回傳值的地方
此時kernel 會回傳 Hello Irene from kernel!
接著 user 開始下達 ioctl command
--> ioctl(fd, SETNUM, &ret)
這是其中一個例子 , SETNUM 便是user 下達的command
而&ret 是 回傳值的指標

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "ioc.h"

int main(int argc, char *argv[])
{
       int fd, ret=10;
       unsigned char buffer[50]={};
       if(argc < 2)
       {
         printf("Usage: /dev/mydev param1.. \n");
         return -1;
       }
       // open dev file
       fd=open(argv[1], O_RDWR);
       if(fd < 0)
       {
           printf("open %s failed\n", argv[1]);
           return -1;
       }
       //read from kernel 
       read(fd, buffer, sizeof(buffer));
       printf("%s\n",buffer);
       // set num to 10
       if (ioctl(fd, SETNUM, &ret)< 0)
       {
           printf("set num failed\n");
           return -1;
       }
       if(ioctl(fd, GETNUM , &ret)< 0)
       {
           printf("get num failed\n");
       }
       printf("get default value=%d\n",ret);
       // exchange number ret =atoi(argv[2]); 
       if (ioctl(fd, XNUM, &ret)<0)
       {
           printf("exchange num failed\n");
           return -1;
        }
       printf("get value = %d\n",ret);
       return 0;
}


最後是 Makefile








上面全部的檔案都存在mydev 資料夾中
/usr/src/kernels/linux-2.6.18/mydev/..

開始執行
make 之後的指令如下












在 insmod之後 dev會被賦予 major number 與 minor number 
開啟新的cmd 執行dmesg就可以看到kernel傳來一條msg
register  chrdev(252,0)
或是可以到 /proc/devices中查看 major number 與 minor number
在mknod時一定要給定對的major 與 minor number
否則會有 找不到 file 的錯誤
執行後可以看到
kernel 傳回一條 hello messages
而kernel也將預設為10 的value改為 user 設定的 100
最後dmesg中可以看到kernel的訊息












研究好多天仍有很多疑問, 有不完備之處之後繼續修改
參考:
 http://csw17.pixnet.net/blog/post/11506143
http://nano-chicken.blogspot.tw/2009/12/linux-modulesv-ioctl.html
http://www.codeproject.com/Articles/112474/A-Simple-Driver-for-Linux-OS
強烈建議大家在寫這個之前先到jollen的網站看相關的 device 觀念教學

2013年8月25日 星期日

Linux Kernel Module 3 - command line argument

1. 由command line傳入的value會被視為global
2. 使用module_param() macro來加入變數
3. 在runtime時, insmod會填入從command line輸入的value到相應的變數
4. module_param()有3個params,依序為 變數名稱, 變數型態, 權限
    module_param(name, type, permission)
   目前支援的type有 byte, short, ushort, int, uint, long, ulong, charp, bool
5.若要傳入array或string可以使用 module_param_array() or module_param_string()
  array的話在type之後要多一個count , module被載入的時候user所指定的array length會被fill到此變
  數中 , NULL也可以
6. 使用 module_param_desc()則可以對某一變數做描述

/*
 * hello_5.c - Demonstrates command line argument passing to a module.
 */
#include &lt;linux/module.h&gt;
#include &lt;linux/moduleparam.h&gt;
#include &lt;linux/kernel.h&gt;
#include &lt;linux/init.h&gt;
#include &lt;linux/stat.h&gt;

MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;Irene&quot;);

static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring =&quot;blah&quot;;
static int myintArray[2]= {6,6};
static int arr_argc = 0;

/*
 * module_param(foo, int , 0000)
 * The first param is the parameters name
 * The second param is it&#39;s data type
 * The final argument is the permissions bits,
 * for exposing parameters in sysfs (if non-zero) at a later stage.
 */

module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, &quot;A short integer&quot;);
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH);
MODULE_PARM_DESC(myint, &quot;An integer&quot;);
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, &quot;A long integer&quot;);
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, &quot;A character string&quot;);

/*
 * module_param_array(name, type, num, perm);
 * The first param is the parameter&#39;s (in this case the array&#39;s)name
 * The second param is the data type of the elements of the array
 * The third argument is a pointer to the variable that will store the number
 * of the elements of the array initialized by the user at module loading time
 * The fourth argument is the permission bits
 */
module_param_array(myintArray, int, &amp;arr_argc, 0000);
MODULE_PARM_DESC(myintArray, &quot;An array of integers&quot;);

static int __init hello_5_init(void)
{
int i;
printk(KERN_INFO &quot;Hello, world 5\n==============\n&quot;);
printk(KERN_INFO &quot;myshort is a short integer: %hd\n&quot;, myshort);
printk(KERN_INFO &quot;myint is an integer: %d\n&quot;, myint);
printk(KERN_INFO &quot;mylong is a long integer: %ld\n&quot;, mylong);
printk(KERN_INFO &quot;mystring is a string: %s\n&quot;,mystring);
for(i=0; i&lt;(sizeof myintArray / sizeof(int));i++)
{
printk(KERN_INFO &quot;myintArray[%d] = %d\n&quot;, i,myintArray[i]);
}
printk(KERN_INFO &quot;got %d arguments for myintArray.\n&quot;, arr_argc);
return 0;
}
static void __exit hello_5_exit(void)
{
printk(KERN_INFO &quot;Goodbye, world 5\n&quot;);
}

module_init(hello_5_init);
module_exit(hello_5_exit); 


利用 insmod hello_5.ko 看output messages ( 利用 cat /var/log/messages)








或是 insmod hello_5.ko myint=25 mystring="Irene" myintArray=1,2









可以看到指定的變數都改value了!




2013年8月24日 星期六

Linux Kernel Module 2 - 顯示 module info

在 Linux  /include/linux/module.h 中可以找到定義一些module基本info的function
for example:

 MODULE_DESCRIPTION(DESC) : 定義MODULE在做些什麼
 MODULE_AUTHOR(AUTHOR) : 明顯的就是告訴大家作者是誰
 MODULE_LICENSE(GPL) : 宣告GPL

至於上面的DESC 和 AUTHOR 可以用 #define 來預先定義
#define DESC "this is description"
#define AUTHOR "I am author"

這是書中 hello_4.c 範例
寫完一樣 make和 insmode
再來執行 modinfo hello_4.ko 就會顯示info了











2013年8月16日 星期五

Linux Kernel Module 1 - 第一支Hello Irene Module

1. 先查詢自己的Linux version
    --> uname -r
    我的版本是 Linux-2.6.18

2. 下載Linux Kernel Source Code (壓縮檔放在/root底下)
     選擇 linux-2.6.18.tar.bz2

3. 把 source code放在 /usr/src/kernels/ 目錄下
    --> tar -jxvf  linux-2.6.18.tar.bz2 -C /usr/src/kernels/
    此時kernels下會有一個linex-2.6.18資料夾

4.進入kernel 目錄
   --> cd /usr/src/kernels/linux-2.6.18

5. 建立存放module的資料夾
   --> mkdir hello

6.開始寫內部程式
   (1) hello-1.c
      --> vim hello-1.c
      --> i  (編輯模式)
      -->按 Esc 回一般模式
      --> :wq (儲存並離開)
     


      (2) Makefile
        --> vim Makefile
        
       


7 --> make
make 之後會產生 hello-1.ko 


8 --> insmod ./hello-1.ko
將hello-1.ko 加入至kernel module中
可以到 /proc/modules/中看到hello-1 module正在kernel中


此時利用 cat /var/log/messages 到messages中會看到 Hello Irene

9. --> rmmod ./hello-1.ko
將hello-1 module移除
此時 /proc/modules/中就看不見 hello-1 module了
而到 /var/log/messages/ 會看到 Goodbye Irene


P.S 想要搜尋Hello與 Goodbye,可使用grep 指令
--> cat /var/log/messages | grep Hello
--> cat /var/log/messages | grep Goodbye

第一次寫kernel的東西~我連linux指令都還不太熟悉@@
希望繼續加油囉!
-------------------------------------------------------------------
參考 :  鳥哥的 Linux私房菜 第三版  ch22,ch26
           The Linux Module Programming Guide 
            





2013年7月11日 星期四

Pointer in C


















圖1
pointer有3種pointer arithmetic
1) result: p指向a[3],q指向a[4]
2) result: p指向a[5],q指向a[7]














圖2
 &b[0] 和 b 一樣代表的是array b的第一個元素














多維陣列也可以想成是一維陣列,ex:第一種寫法
p指向第一個元素,接著一直向下指
多維陣列也是多個一維陣列組成,ex:第二種寫法
i 是row的index
p先指向第一行陣列,c[i]此時代表的是第一行陣列第一個元素的addr
p會先加完第一行所有列的元素在指向第二行

<P.s 1>
終止條件寫成 p< &c[NUM_ROWS][NUM_COLS]可能會有錯
因為c[NUM_ROWS][NUM_COLS]這一個元素已在array c預期的大小之外
因此他的addr可能是一個任意位址
若p的addr剛好小於此元素的位址就會造成錯誤。

<P.s 2>
while(*a!=0)
  a++;    //wrong
雖然 a 是 array name也可以當作指標,但不能assign 給 a 新的值
上述 a++指移動位址,因此錯誤
可以給一個 pointer指向a
再移動 p (assign p 新的值)
p=a;
while(*p!=0)
   p++;   //correct


全國推廣動物認領養平台串聯貼紙

全國推廣動物認領養平台串聯貼紙