2012年8月17日 星期五
ubuntu 12.04 install apache/phpmyadmin
1. sudo apt-get install apache2
2. sudo apt-get install php5 libapache2-mod-php5
3. sudo apt-get install mysql-server
4. sudo apt-get install libapache2-mod-auth-mysql php5-mysql phpmyadmin
5. cd /var/www
6. sudo ln -s /usr/share/phpmyadmin .
7. sudo /etc/init.d/apache2 restart
open web browser http://127.0.0.1/phpmyadmin
2012年8月16日 星期四
samba mount on linux
mount -t cifs -o username="username",password="password" //192.168.1.x/src /dist
安裝 qemu on ubuntu 12.04(包含執行 ARM 指令集的 QEMU emulator)
安裝:
1. 安裝gcc-4.6
2. apt-get install libsdl1.2debian libsdl1.2-dev zlib1g-dev
3. download qemu-1.1.1-1.tar.bz2
4. mv qemu-1.1.1-1.tar.bz2 /usr/local
5. cd /usr/local
6. tar jxvf qemu-1.1.1-1.tar.bz2
7. cd qemu-1.1.1
8. ./configure --cc=/usr/bin/gcc-4.6 --host-cc=/usr/bin/gcc-4.6 --target-list=arm-softmmu
測試:
./arm-softmmu/qemu-system-arm -kernel /home/walter/arm-test/zImage.integrator -initrd /home/walter/arm-test/arm_root.img -nographic -append "console=ttyAMA0"
1. 安裝gcc-4.6
2. apt-get install libsdl1.2debian libsdl1.2-dev zlib1g-dev
3. download qemu-1.1.1-1.tar.bz2
4. mv qemu-1.1.1-1.tar.bz2 /usr/local
5. cd /usr/local
6. tar jxvf qemu-1.1.1-1.tar.bz2
7. cd qemu-1.1.1
8. ./configure --cc=/usr/bin/gcc-4.6 --host-cc=/usr/bin/gcc-4.6 --target-list=arm-softmmu
測試:
./arm-softmmu/qemu-system-arm -kernel /home/walter/arm-test/zImage.integrator -initrd /home/walter/arm-test/arm_root.img -nographic -append "console=ttyAMA0"
2012年1月6日 星期五
基礎 Linux Device Driver 驅動程式#11 (procfs-read only)
procfs 指的是位於 /proc 的虛擬檔案系統(實體在 RAM內), 內含 kernel 與各驅動程式的設定選項。
/proc 內的大多數檔案都是唯讀的,像 /proc/cpuinfo , /proc/meminfo 等等...
但也有些是可讓使用者設定的。 像 /proc/sys/kernel/printk 就是了。
如果是要建立唯讀的 procfs,是呼叫 create_proc_read_entry。
定義在 include/linux/proc_fs.h
struct proc_dir_entry *create_proc_read_entry(const char *name,
mode_t mode, struct proc_dir_entry *base,
read_proc_t *read_proc, void * data)
name: 在 /proc 之內的檔案名稱
mode: 權限(可 NULL)
base: 上層目錄(可 NULL)
read_proc: 讀取處理函式
data: 私有資料(可 NULL)
read_proc 引數要傳入讀取時呼叫的處理函式指標。
這個函式的 prototype 已 typedef 為 read_proc_t, 如下所示:
typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
page: Kernel 配置的記憶體空間
start: 驅動程式寫入資料的開始位址指標(由驅動程式回傳)
off: 驅動程式傳回資料的偏移量
count: User space 一次讀回的量
eof: 通知已達資料終點(由驅動程式回傳)
data: create_proc_read_entry() 設定的私有資料
page 引數是 kernel 分配的記憶體,所以驅動程式可以直接寫入,
但只分配了一個 page 的大小(PAGE_SIZE),所以寫入資料起過 PAGE_SIZE 就會破壞記憶體內容。
驅動程式在卸載的時候,要呼叫 remove_proc_entry() 清除 procfs 的內容。
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
好吧, 就來試一下吧。
test_proc.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#define PROC_NAME "test_proc_info"
#define BUF_SIZE 10
static char buf[BUF_SIZE] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'};
static int test_proc_read(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len = BUF_SIZE;
if (len > PAGE_SIZE)
return -ENOBUFS;
len = sprintf(page, "%s\n", buf);
return len;
}
static int test_proc_init(void)
{
struct proc_dir_entry *entry;
int ret = 0;
printk(KERN_ALERT "Procfs init...\n");
entry = create_proc_read_entry(PROC_NAME,
S_IRUGO | S_IWUGO,
NULL,
test_proc_read,
NULL);
if (entry == NULL) {
ret = -ENOMEM;
goto out;
}
out:
return ret;
}
static void test_proc_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_ALERT "Procfs exit.\n");
}
module_init(test_proc_init);
module_exit(test_proc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_proc_read module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_proc.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
開始測試, go........
# ls
Makefile test_proc.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_proc_read modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_proc_read/test_proc.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_proc_read/test_proc.mod.o
LD [M] /opt/test_driver/my_driver/test_proc_read/test_proc.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_proc.ko
Procfs init...
# ls -l /proc/test_proc_info
-rw-rw-rw- 1 root root 0 2012-01-06 16:35 /proc/test_proc_info
# cat /proc/test_proc_info
abcdefghij
# rmmod test_proc
Procfs exit.
# ls -l /proc/test_proc_info
ls: 無法存取 /proc/test_proc_info: 沒有此一檔案或目錄
你看看,你看看,是不是這麼一回事呢?
哈^^
procfs - read write 待續......
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
/proc 內的大多數檔案都是唯讀的,像 /proc/cpuinfo , /proc/meminfo 等等...
但也有些是可讓使用者設定的。 像 /proc/sys/kernel/printk 就是了。
如果是要建立唯讀的 procfs,是呼叫 create_proc_read_entry。
定義在 include/linux/proc_fs.h
struct proc_dir_entry *create_proc_read_entry(const char *name,
mode_t mode, struct proc_dir_entry *base,
read_proc_t *read_proc, void * data)
name: 在 /proc 之內的檔案名稱
mode: 權限(可 NULL)
base: 上層目錄(可 NULL)
read_proc: 讀取處理函式
data: 私有資料(可 NULL)
read_proc 引數要傳入讀取時呼叫的處理函式指標。
這個函式的 prototype 已 typedef 為 read_proc_t, 如下所示:
typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
page: Kernel 配置的記憶體空間
start: 驅動程式寫入資料的開始位址指標(由驅動程式回傳)
off: 驅動程式傳回資料的偏移量
count: User space 一次讀回的量
eof: 通知已達資料終點(由驅動程式回傳)
data: create_proc_read_entry() 設定的私有資料
page 引數是 kernel 分配的記憶體,所以驅動程式可以直接寫入,
但只分配了一個 page 的大小(PAGE_SIZE),所以寫入資料起過 PAGE_SIZE 就會破壞記憶體內容。
驅動程式在卸載的時候,要呼叫 remove_proc_entry() 清除 procfs 的內容。
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
好吧, 就來試一下吧。
test_proc.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#define PROC_NAME "test_proc_info"
#define BUF_SIZE 10
static char buf[BUF_SIZE] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'};
static int test_proc_read(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len = BUF_SIZE;
if (len > PAGE_SIZE)
return -ENOBUFS;
len = sprintf(page, "%s\n", buf);
return len;
}
static int test_proc_init(void)
{
struct proc_dir_entry *entry;
int ret = 0;
printk(KERN_ALERT "Procfs init...\n");
entry = create_proc_read_entry(PROC_NAME,
S_IRUGO | S_IWUGO,
NULL,
test_proc_read,
NULL);
if (entry == NULL) {
ret = -ENOMEM;
goto out;
}
out:
return ret;
}
static void test_proc_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_ALERT "Procfs exit.\n");
}
module_init(test_proc_init);
module_exit(test_proc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_proc_read module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_proc.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
開始測試, go........
# ls
Makefile test_proc.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_proc_read modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_proc_read/test_proc.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_proc_read/test_proc.mod.o
LD [M] /opt/test_driver/my_driver/test_proc_read/test_proc.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_proc.ko
Procfs init...
# ls -l /proc/test_proc_info
-rw-rw-rw- 1 root root 0 2012-01-06 16:35 /proc/test_proc_info
# cat /proc/test_proc_info
abcdefghij
# rmmod test_proc
Procfs exit.
# ls -l /proc/test_proc_info
ls: 無法存取 /proc/test_proc_info: 沒有此一檔案或目錄
你看看,你看看,是不是這麼一回事呢?
哈^^
procfs - read write 待續......
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
2012年1月5日 星期四
基礎 Linux Device Driver 驅動程式#10 (select/poll)
相信各位都有在Linux上寫程式的經驗,
當您程式裡呼叫 open 時,Linux 預設會以 blocking mode的方式開啟,
block 是指當 process 為了等待某件事的發生,而進入 sleep 狀態的情形。
像 read 就是其中一種,當沒資料讀取時,process 就會被 block。
write 也是一樣,在寫入資料時,寫入對象還無法處理資料時,一樣會 block。
對某些程式來說,如果 read 系統呼叫被 block 的話,有時就會有設計上的問題,
所以為了避免這種問題發生,Linux 就準備了以下方式。
1. Non-blocking 模式
啟用 non-blocking 模式後,不管是 read 還是 write 就不會被 block住。但會傳回 errno 錯誤碼,
這時就必須自已再做讀寫的動作。
想使用 non-blocking 模式的話,可在 open() 開檔時指定 O_NONBLOCK。
2. 同時執行多個同步 I/O 工作
同時執行多個同步 I/O 工作 指的是使用 select 系統呼叫的做法。
select 系統呼叫本身會被 block, 但可指定 timeout。
使用 select() 的時候,有幾個常用的函式巨集,可參考 man 2 select。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
還有一個和 select()很類似的 poll()系統呼叫,以基本功能來說,select()與poll()都一樣,
但指定的 file handler的方式不一樣,且指定多個 file handler 的時候,poll()走訪所有
file handler的速度比較快。 可參考 man 2 poll。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。
Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll方法會在kernel 處理 select 與 poll 之類的系統呼叫時用到。它必須的執行工作如下:
1. 在 wait queue 豋記。
2. 傳回目前可以操作的狀態。
在呼叫同時執行多個同步 I/O 工作的系統呼叫時,block 直到狀態變化的動作,指的是在 kernel裡面 sleep。
如果要 sleep的話,要先準備 wait queue(wait_queue_head_t),這個由驅動程式負責提供。
豋記 wait queue 的工作可透過 poll_wait()完成,它定義在 include/linux/poll.h
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
在呼叫同時執行多個同步 I/O 工作的系統呼叫時,解除 block 的時機,是驅動程式透過傳給 poll_wait()
的 wait queue 被喚醒的時候。Kernel 在被 wait queue 喚醒之後,會再次呼叫驅動程式的 poll()
確認是否成為等待中的狀態(可寫入或可讀出),如果是的話,FD_ISSET 巨集就會成立。
想判斷是不是這種狀態的話,還是需要驅動程式提供資訊才行,這個資訊就是透過poll()方法的傳回值來表示。
傳回值要透過 include/linux/poll.h 定義的巨集 OR 起來表示,下面是常用到的組合。
POLLIN|POLLRDNORM 可讀取
POLLOUT|POLLWRNORM 可寫入
POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM 可讀寫
POLLERR 發生錯誤
POLLHUP 裝置離線(EOF)
說了那麼多,還倒不如寫個程式比較好了解。
剛試了一下,果然新舊版核心還是有差,幾個問題,稍為提出來討論一下:
1. 要用 kmalloc 及 kfree 的話,要 include <linux/slab.h>
註:其實在上一個示範程式就已有這問題了。
請參考: 基礎 Linux Device Driver 驅動程式#9 (IOCTL)
2. void init_MUTEX (struct semaphore *sem); /* 新版 kernel 已不適用 */
改用
void sema_init (struct semaphore *sem, int val);
test_select.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "test_select"
static unsigned int test_select_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_select_cdev;
static unsigned int timeout_value = 10;
struct test_select_data {
struct timer_list timeout;
spinlock_t lock;
wait_queue_head_t read_wait;
int timeout_done;
struct semaphore sem;
};
unsigned int test_select_poll(struct file *filp, poll_table *wait)
{
struct test_select_data *data = filp->private_data;
unsigned int mask = POLLOUT|POLLWRNORM;
printk(KERN_ALERT "Call test_select_poll.\n");
if (data == NULL)
return -EBADFD;
down(&data->sem);
poll_wait(filp, &data->read_wait, wait);
if (data->timeout_done == 1) { /* readable */
mask |= POLLIN|POLLRDNORM;
}
up(&data->sem);
printk(KERN_ALERT "%s returned (mask 0x%x)\n", __func__, mask);
}
static void test_select_timeout(unsigned long arg)
{
struct test_select_data *data = (struct test_select_data*)arg;
unsigned long flags;
printk(KERN_ALERT "Call test_select_timeout.\n");
spin_lock_irqsave(&data->lock, flags);
data->timeout_done = 1;
wake_up_interruptible(&data->read_wait);
spin_unlock_irqrestore(&data->lock, flags);
}
ssize_t test_select_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
return -EFAULT;
}
ssize_t test_select_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_select_data *data = filp->private_data;
int i;
unsigned char val;
int retval;
if (down_interruptible(&data->sem))
return -ERESTARTSYS;
if (data->timeout_done == 0) { /* no read */
up(&data->sem);
if (filp->f_flags & O_NONBLOCK) /* non-blocking mode */
return -EAGAIN;
do {
retval = wait_event_interruptible_timeout(
data->read_wait,
data->timeout_done == 1,
1*HZ);
if (retval == -ERESTARTSYS)
return -ERESTARTSYS;
} while (retval == 0); /* timeout elapsed */
if (down_interruptible(&data->sem))
return -ERESTARTSYS;
}
val = 0xff;
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;
out:
data->timeout_done = 0;
/* restart timer */
mod_timer(&data->timeout, jiffies + timeout_value*HZ);
up(&data->sem);
return retval;
}
static int test_select_close(struct inode *inode, struct file *filp)
{
struct test_select_data *data = filp->private_data;
printk(KERN_ALERT "Call test_select_close.\n");
if (data) {
del_timer_sync(&data->timeout);
kfree(data);
}
return 0;
}
static int test_select_open(struct inode *inode, struct file *filp)
{
struct test_select_data *data;
printk(KERN_ALERT "Call test_select_open.\n");
data = kmalloc(sizeof(struct test_select_data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
/* initialize members */
spin_lock_init(&data->lock);
init_waitqueue_head(&data->read_wait);
// init_MUTEX(&data->sem); /* 新版 kernel 已不適用 */
sema_init(&data->sem, 1); /* 改用 sema_init */
init_timer(&data->timeout);
data->timeout.function = test_select_timeout;
data->timeout.data = (unsigned long)data;
filp->private_data = data;
/* start timer */
data->timeout_done = 0;
mod_timer(&data->timeout, jiffies + timeout_value*HZ);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_select_open,
.release = test_select_close,
.read = test_select_read,
.write = test_select_write,
.poll = test_select_poll,
};
static int test_select_init(void)
{
dev_t dev = MKDEV(test_select_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
test_select_major = MAJOR(dev);
cdev_init(&test_select_cdev, &fops);
cdev_ret = cdev_add(&test_select_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_select_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_select_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void test_select_exit(void)
{
dev_t dev = MKDEV(test_select_major, 0);
cdev_del(&test_select_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed\n", DRIVER_NAME);
}
module_init(test_select_init);
module_exit(test_select_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_select module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_select.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
test.c 如下:
/*****************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#define DEVFILE "/dev/test_select0"
int main()
{
int fd;
fd_set rfds;
struct timeval tv;
int retval;
unsigned char buf;
ssize_t sz;
int i;
fd = open(DEVFILE, O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
do {
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = 5;
tv.tv_usec = 0;
printf("select() ...\n");
retval = select(fd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1) {
perror("select");
break;
}
if (retval) {
break;
}
} while (retval == 0); /* timeout elapsed */
if (FD_ISSET(fd, &rfds)) {
printf("read() ...\n");
sz = read(fd, &buf, 1);
printf("read() %d\n", sz);
printf("%02x ", buf);
printf("\n");
}
close(fd);
return 0;
}
/*****************************************************************************/
執行結果如下:
# ls
Makefile test_code test_select.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_select/test_select.o
/opt/test_driver/my_driver/test_select/test_select.c: In function ‘test_select_poll’:
/opt/test_driver/my_driver/test_select/test_select.c:44:1: warning: control reaches end of non-void function
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_select/test_select.mod.o
LD [M] /opt/test_driver/my_driver/test_select/test_select.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
Makefile Module.symvers test_select.c test_select.mod.c test_select.o
modules.order test_code test_select.ko test_select.mod.o
# cd test_code/
# ls
test.c
# gcc test.c -o test
# ls
test test.c
# cd ..
# insmod ./test_select.ko
test_select driver(major: 250) installed.
# mknod /dev/test_select0 c 250 0
# ./test_code/test
select() ...
read() ...
... 經過 10秒 ...
read() 1
ff
# dmesg | tail
... 以上略過 ...
Call test_select_open.
Call test_select_poll.
test_select_poll returned (mask 0x104)
Call test_select_timeout.
Call test_select_close.
# rm -rf test_code/test
# rm /dev/test_select0
# rmmod test_select
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/test_select/.tmp_versions
CLEAN /opt/test_driver/my_driver/test_select/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
OK, 一切就是如此的順利,世界就是如此的美好 ^^
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
當您程式裡呼叫 open 時,Linux 預設會以 blocking mode的方式開啟,
block 是指當 process 為了等待某件事的發生,而進入 sleep 狀態的情形。
像 read 就是其中一種,當沒資料讀取時,process 就會被 block。
write 也是一樣,在寫入資料時,寫入對象還無法處理資料時,一樣會 block。
對某些程式來說,如果 read 系統呼叫被 block 的話,有時就會有設計上的問題,
所以為了避免這種問題發生,Linux 就準備了以下方式。
1. Non-blocking 模式
啟用 non-blocking 模式後,不管是 read 還是 write 就不會被 block住。但會傳回 errno 錯誤碼,
這時就必須自已再做讀寫的動作。
想使用 non-blocking 模式的話,可在 open() 開檔時指定 O_NONBLOCK。
2. 同時執行多個同步 I/O 工作
同時執行多個同步 I/O 工作 指的是使用 select 系統呼叫的做法。
select 系統呼叫本身會被 block, 但可指定 timeout。
使用 select() 的時候,有幾個常用的函式巨集,可參考 man 2 select。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
還有一個和 select()很類似的 poll()系統呼叫,以基本功能來說,select()與poll()都一樣,
但指定的 file handler的方式不一樣,且指定多個 file handler 的時候,poll()走訪所有
file handler的速度比較快。 可參考 man 2 poll。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。
Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll方法會在kernel 處理 select 與 poll 之類的系統呼叫時用到。它必須的執行工作如下:
1. 在 wait queue 豋記。
2. 傳回目前可以操作的狀態。
在呼叫同時執行多個同步 I/O 工作的系統呼叫時,block 直到狀態變化的動作,指的是在 kernel裡面 sleep。
如果要 sleep的話,要先準備 wait queue(wait_queue_head_t),這個由驅動程式負責提供。
豋記 wait queue 的工作可透過 poll_wait()完成,它定義在 include/linux/poll.h
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
在呼叫同時執行多個同步 I/O 工作的系統呼叫時,解除 block 的時機,是驅動程式透過傳給 poll_wait()
的 wait queue 被喚醒的時候。Kernel 在被 wait queue 喚醒之後,會再次呼叫驅動程式的 poll()
確認是否成為等待中的狀態(可寫入或可讀出),如果是的話,FD_ISSET 巨集就會成立。
想判斷是不是這種狀態的話,還是需要驅動程式提供資訊才行,這個資訊就是透過poll()方法的傳回值來表示。
傳回值要透過 include/linux/poll.h 定義的巨集 OR 起來表示,下面是常用到的組合。
POLLIN|POLLRDNORM 可讀取
POLLOUT|POLLWRNORM 可寫入
POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM 可讀寫
POLLERR 發生錯誤
POLLHUP 裝置離線(EOF)
說了那麼多,還倒不如寫個程式比較好了解。
剛試了一下,果然新舊版核心還是有差,幾個問題,稍為提出來討論一下:
1. 要用 kmalloc 及 kfree 的話,要 include <linux/slab.h>
註:其實在上一個示範程式就已有這問題了。
請參考: 基礎 Linux Device Driver 驅動程式#9 (IOCTL)
2. void init_MUTEX (struct semaphore *sem); /* 新版 kernel 已不適用 */
改用
void sema_init (struct semaphore *sem, int val);
test_select.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "test_select"
static unsigned int test_select_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_select_cdev;
static unsigned int timeout_value = 10;
struct test_select_data {
struct timer_list timeout;
spinlock_t lock;
wait_queue_head_t read_wait;
int timeout_done;
struct semaphore sem;
};
unsigned int test_select_poll(struct file *filp, poll_table *wait)
{
struct test_select_data *data = filp->private_data;
unsigned int mask = POLLOUT|POLLWRNORM;
printk(KERN_ALERT "Call test_select_poll.\n");
if (data == NULL)
return -EBADFD;
down(&data->sem);
poll_wait(filp, &data->read_wait, wait);
if (data->timeout_done == 1) { /* readable */
mask |= POLLIN|POLLRDNORM;
}
up(&data->sem);
printk(KERN_ALERT "%s returned (mask 0x%x)\n", __func__, mask);
}
static void test_select_timeout(unsigned long arg)
{
struct test_select_data *data = (struct test_select_data*)arg;
unsigned long flags;
printk(KERN_ALERT "Call test_select_timeout.\n");
spin_lock_irqsave(&data->lock, flags);
data->timeout_done = 1;
wake_up_interruptible(&data->read_wait);
spin_unlock_irqrestore(&data->lock, flags);
}
ssize_t test_select_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
return -EFAULT;
}
ssize_t test_select_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_select_data *data = filp->private_data;
int i;
unsigned char val;
int retval;
if (down_interruptible(&data->sem))
return -ERESTARTSYS;
if (data->timeout_done == 0) { /* no read */
up(&data->sem);
if (filp->f_flags & O_NONBLOCK) /* non-blocking mode */
return -EAGAIN;
do {
retval = wait_event_interruptible_timeout(
data->read_wait,
data->timeout_done == 1,
1*HZ);
if (retval == -ERESTARTSYS)
return -ERESTARTSYS;
} while (retval == 0); /* timeout elapsed */
if (down_interruptible(&data->sem))
return -ERESTARTSYS;
}
val = 0xff;
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;
out:
data->timeout_done = 0;
/* restart timer */
mod_timer(&data->timeout, jiffies + timeout_value*HZ);
up(&data->sem);
return retval;
}
static int test_select_close(struct inode *inode, struct file *filp)
{
struct test_select_data *data = filp->private_data;
printk(KERN_ALERT "Call test_select_close.\n");
if (data) {
del_timer_sync(&data->timeout);
kfree(data);
}
return 0;
}
static int test_select_open(struct inode *inode, struct file *filp)
{
struct test_select_data *data;
printk(KERN_ALERT "Call test_select_open.\n");
data = kmalloc(sizeof(struct test_select_data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
/* initialize members */
spin_lock_init(&data->lock);
init_waitqueue_head(&data->read_wait);
// init_MUTEX(&data->sem); /* 新版 kernel 已不適用 */
sema_init(&data->sem, 1); /* 改用 sema_init */
init_timer(&data->timeout);
data->timeout.function = test_select_timeout;
data->timeout.data = (unsigned long)data;
filp->private_data = data;
/* start timer */
data->timeout_done = 0;
mod_timer(&data->timeout, jiffies + timeout_value*HZ);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_select_open,
.release = test_select_close,
.read = test_select_read,
.write = test_select_write,
.poll = test_select_poll,
};
static int test_select_init(void)
{
dev_t dev = MKDEV(test_select_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
test_select_major = MAJOR(dev);
cdev_init(&test_select_cdev, &fops);
cdev_ret = cdev_add(&test_select_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_select_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_select_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void test_select_exit(void)
{
dev_t dev = MKDEV(test_select_major, 0);
cdev_del(&test_select_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed\n", DRIVER_NAME);
}
module_init(test_select_init);
module_exit(test_select_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_select module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_select.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
test.c 如下:
/*****************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#define DEVFILE "/dev/test_select0"
int main()
{
int fd;
fd_set rfds;
struct timeval tv;
int retval;
unsigned char buf;
ssize_t sz;
int i;
fd = open(DEVFILE, O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
do {
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = 5;
tv.tv_usec = 0;
printf("select() ...\n");
retval = select(fd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1) {
perror("select");
break;
}
if (retval) {
break;
}
} while (retval == 0); /* timeout elapsed */
if (FD_ISSET(fd, &rfds)) {
printf("read() ...\n");
sz = read(fd, &buf, 1);
printf("read() %d\n", sz);
printf("%02x ", buf);
printf("\n");
}
close(fd);
return 0;
}
/*****************************************************************************/
執行結果如下:
# ls
Makefile test_code test_select.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_select/test_select.o
/opt/test_driver/my_driver/test_select/test_select.c: In function ‘test_select_poll’:
/opt/test_driver/my_driver/test_select/test_select.c:44:1: warning: control reaches end of non-void function
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_select/test_select.mod.o
LD [M] /opt/test_driver/my_driver/test_select/test_select.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
Makefile Module.symvers test_select.c test_select.mod.c test_select.o
modules.order test_code test_select.ko test_select.mod.o
# cd test_code/
# ls
test.c
# gcc test.c -o test
# ls
test test.c
# cd ..
# insmod ./test_select.ko
test_select driver(major: 250) installed.
# mknod /dev/test_select0 c 250 0
# ./test_code/test
select() ...
read() ...
... 經過 10秒 ...
read() 1
ff
# dmesg | tail
... 以上略過 ...
Call test_select_open.
Call test_select_poll.
test_select_poll returned (mask 0x104)
Call test_select_timeout.
Call test_select_close.
# rm -rf test_code/test
# rm /dev/test_select0
# rmmod test_select
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/test_select/.tmp_versions
CLEAN /opt/test_driver/my_driver/test_select/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
OK, 一切就是如此的順利,世界就是如此的美好 ^^
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
2012年1月3日 星期二
基礎 Linux Device Driver 驅動程式#9 (IOCTL)
IOCTL 是一種系統呼叫介面,user process 呼叫 ioctl() 即可對驅動程式送出系統呼叫,
如此會呼叫驅動程式的 IOCTL 處理函式,也可跟驅動程式交換資料。
交換資料的格式,可由驅動程式開發者自由決定。
IOCTL 方法的 prototype如下:
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);
inode: 是開啟裝置檔的相關資訊。
filp: 可取出驅動程式的私有資料。
cmd: 是 IOCTL 的指令,且不可省略,驅動程式可由它得知 user process 想做什麼。
arg: 是 ioctl()可變引數(...)的參數,內含 user process的指標,但驅動程式不得
直接讀寫這個指標,必須透過copy_from_user() 及 copy_to_user() 讀寫資料。
但,各位可能要注意一個小地方囉,我目前用的 linux kernel 是 2.6.38,
ioctl 這個 handler 在 2.3.36的核心版本已不用這個名稱了,
改用以下兩個:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
所以要注意喔,如果各位也是和我用一樣新核心的話,就....你知道的。
我示範的 code 也會用 unlocked_ioctl 這個 handler。
IOCTL 指令的格式是以巨集定義的。驅動程式與user process 的程式共用一個標頭檔。
四個巨集分別如下:
_IO(): 無引數的 IOCTL。
_IOR(): 從驅動程式讀取資料。
_IOW(): 把資料寫給驅動程式。
_IOWR(): 與驅動程式讀寫資料。
定義在 include/asm-generic/ioctl.h
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
驅動程式可使用 _IO_SIZE 巨集得知傳給 ioctl() 可變引數的結構大小。
也是定義在 include/asm-generic/ioctl.h
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
在讀寫 user space 的指標時,可以呼叫 access_ok() 判斷指標可否讀讀寫。
定義在 include/asm-generic/uaccess.h
#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))
當然有些時候,會只限定root才能使用 ioctl,一般使用者不能使用,可用 int capable(int cap);
再來寫個 code 跑一下吧^^
但在這之前,我們先看一下我們要 include 的標頭檔,如下:
test_ioctl.h
/*****************************************************************************/
#ifndef _IOCTL_TEST_H
#define _IOCTL_TEST_H
#include <linux/ioctl.h>
struct ioctl_arg {
unsigned int reg;
unsigned int val;
};
/* 這裡要找一個沒用到的號碼,請參考 Documentation/ioctl/ioctl-number.txt */
#define IOC_MAGIC '\x66'
/* 您要的動作 */
#define IOCTL_VALSET _IOW(IOC_MAGIC, 0, struct ioctl_arg)
#define IOCTL_VALGET _IOR(IOC_MAGIC, 1, struct ioctl_arg)
#define IOCTL_VALGET_NUM _IOR(IOC_MAGIC, 2, int)
#define IOCTL_VALSET_NUM _IOW(IOC_MAGIC, 3, int)
#define IOCTL_VAL_MAXNR 3
#endif
/*****************************************************************************/
test_ioctl.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "test_ioctl.h"
#define DRIVER_NAME "test_ioctl"
static unsigned int test_ioctl_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_ioctl_cdev;
static int ioctl_num = 0;
struct test_ioctl_data {
unsigned char val;
rwlock_t lock;
};
static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
int retval;
unsigned char val;
struct ioctl_arg data;
memset(&data, 0, sizeof(data));
switch (cmd) {
case IOCTL_VALSET:
/*
if (!capable(CAP_SYS_ADMIN)) {
retval = -EPERM;
goto done;
}
if (!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
retval = -EFAULT;
goto done;
}
printk(KERN_ALERT "IOCTL set val:%x .\n", data.val);
write_lock(&ioctl_data->lock);
ioctl_data->val = data.val;
write_unlock(&ioctl_data->lock);
break;
case IOCTL_VALGET:
/*
if (!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);
data.val = val;
if (copy_to_user((int __user *)arg, &data, sizeof(data)) ) {
retval = -EFAULT;
goto done;
}
break;
case IOCTL_VALGET_NUM:
retval = __put_user(ioctl_num, (int __user *)arg);
break;
case IOCTL_VALSET_NUM:
/*
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
*/
ioctl_num = arg;
break;
default:
retval = -ENOTTY;
}
done:
return retval;
}
ssize_t test_ioctl_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
unsigned char val;
int retval;
int i = 0;
read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);
for (;i < count ;i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;
out:
return retval;
}
static int test_ioctl_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "%s call.\n", __func__);
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}
return 0;
}
static int test_ioctl_open(struct inode *inode, struct file *filp)
{
struct test_ioctl_data *ioctl_data;
printk(KERN_ALERT "%s call.\n", __func__);
ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
if (ioctl_data == NULL)
return -ENOMEM;
rwlock_init(&ioctl_data->lock);
ioctl_data->val = 0xFF;
filp->private_data = ioctl_data;
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_ioctl_open,
.release = test_ioctl_close,
.read = test_ioctl_read,
.unlocked_ioctl = test_ioctl_ioctl,
};
static int test_ioctl_init(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
test_ioctl_major = MAJOR(dev);
cdev_init(&test_ioctl_cdev, &fops);
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_ioctl_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_ioctl_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void test_ioctl_exit(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
cdev_del(&test_ioctl_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(test_ioctl_init);
module_exit(test_ioctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_ioctl module.");
/*****************************************************************************/
那當然還少不了測試程式囉。
test.c 如下:
/*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "../test_ioctl.h"
#define DEVFILE "/dev/t1"
int main(void)
{
struct ioctl_arg cmd;
int fd;
long ret;
int num = 0;
fd = open(DEVFILE, O_RDWR);
if (fd == -1)
perror("open");
memset(&cmd, 0, sizeof(cmd));
ret = ioctl(fd, IOCTL_VALGET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("val %x\n", cmd.val);
cmd.val = 0xCC;
ret = ioctl(fd, IOCTL_VALSET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
ret = ioctl(fd, IOCTL_VALGET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("val %x\n", cmd.val);
num = 100;
ret = ioctl(fd, IOCTL_VALSET_NUM, num);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
ret = ioctl(fd, IOCTL_VALGET_NUM, &num);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("num %d\n", num);
if (close(fd) != 0)
perror("close");
return 0;
}
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_ioctl.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
開始測試囉^^
# ls
Makefile test_code test_ioctl.c test_ioctl.h
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_ioctl/test_ioctl.o
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c: In function ‘test_ioctl_ioctl’:
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c:23:6: warning: ‘retval’ may be used uninitialized in this function
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_ioctl/test_ioctl.mod.o
LD [M] /opt/test_driver/my_driver/test_ioctl/test_ioctl.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_ioctl.ko
test_ioctl driver(major: 248) installed.
# cd test_code/
# gcc test.c -o test
# cd ..
# mknod /dev/t1 c 248 0
# ./test_code/test
val ff
val cc
num 100
# dmesg | tail
... /* 這裡略過 */
test_ioctl_open call.
IOCTL set val:cc .
test_ioctl_close call.
# rm /dev/t1
# rmmod test_ioctl
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/test_ioctl/.tmp_versions
CLEAN /opt/test_driver/my_driver/test_ioctl/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
完成 ^^
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
如此會呼叫驅動程式的 IOCTL 處理函式,也可跟驅動程式交換資料。
交換資料的格式,可由驅動程式開發者自由決定。
IOCTL 方法的 prototype如下:
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);
inode: 是開啟裝置檔的相關資訊。
filp: 可取出驅動程式的私有資料。
cmd: 是 IOCTL 的指令,且不可省略,驅動程式可由它得知 user process 想做什麼。
arg: 是 ioctl()可變引數(...)的參數,內含 user process的指標,但驅動程式不得
直接讀寫這個指標,必須透過copy_from_user() 及 copy_to_user() 讀寫資料。
但,各位可能要注意一個小地方囉,我目前用的 linux kernel 是 2.6.38,
ioctl 這個 handler 在 2.3.36的核心版本已不用這個名稱了,
改用以下兩個:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
所以要注意喔,如果各位也是和我用一樣新核心的話,就....你知道的。
我示範的 code 也會用 unlocked_ioctl 這個 handler。
IOCTL 指令的格式是以巨集定義的。驅動程式與user process 的程式共用一個標頭檔。
四個巨集分別如下:
_IO(): 無引數的 IOCTL。
_IOR(): 從驅動程式讀取資料。
_IOW(): 把資料寫給驅動程式。
_IOWR(): 與驅動程式讀寫資料。
定義在 include/asm-generic/ioctl.h
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
驅動程式可使用 _IO_SIZE 巨集得知傳給 ioctl() 可變引數的結構大小。
也是定義在 include/asm-generic/ioctl.h
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
在讀寫 user space 的指標時,可以呼叫 access_ok() 判斷指標可否讀讀寫。
定義在 include/asm-generic/uaccess.h
#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))
當然有些時候,會只限定root才能使用 ioctl,一般使用者不能使用,可用 int capable(int cap);
再來寫個 code 跑一下吧^^
但在這之前,我們先看一下我們要 include 的標頭檔,如下:
test_ioctl.h
/*****************************************************************************/
#ifndef _IOCTL_TEST_H
#define _IOCTL_TEST_H
#include <linux/ioctl.h>
struct ioctl_arg {
unsigned int reg;
unsigned int val;
};
/* 這裡要找一個沒用到的號碼,請參考 Documentation/ioctl/ioctl-number.txt */
#define IOC_MAGIC '\x66'
/* 您要的動作 */
#define IOCTL_VALSET _IOW(IOC_MAGIC, 0, struct ioctl_arg)
#define IOCTL_VALGET _IOR(IOC_MAGIC, 1, struct ioctl_arg)
#define IOCTL_VALGET_NUM _IOR(IOC_MAGIC, 2, int)
#define IOCTL_VALSET_NUM _IOW(IOC_MAGIC, 3, int)
#define IOCTL_VAL_MAXNR 3
#endif
/*****************************************************************************/
test_ioctl.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "test_ioctl.h"
#define DRIVER_NAME "test_ioctl"
static unsigned int test_ioctl_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_ioctl_cdev;
static int ioctl_num = 0;
struct test_ioctl_data {
unsigned char val;
rwlock_t lock;
};
static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
int retval;
unsigned char val;
struct ioctl_arg data;
memset(&data, 0, sizeof(data));
switch (cmd) {
case IOCTL_VALSET:
/*
if (!capable(CAP_SYS_ADMIN)) {
retval = -EPERM;
goto done;
}
if (!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
retval = -EFAULT;
goto done;
}
printk(KERN_ALERT "IOCTL set val:%x .\n", data.val);
write_lock(&ioctl_data->lock);
ioctl_data->val = data.val;
write_unlock(&ioctl_data->lock);
break;
case IOCTL_VALGET:
/*
if (!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);
data.val = val;
if (copy_to_user((int __user *)arg, &data, sizeof(data)) ) {
retval = -EFAULT;
goto done;
}
break;
case IOCTL_VALGET_NUM:
retval = __put_user(ioctl_num, (int __user *)arg);
break;
case IOCTL_VALSET_NUM:
/*
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
*/
ioctl_num = arg;
break;
default:
retval = -ENOTTY;
}
done:
return retval;
}
ssize_t test_ioctl_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
unsigned char val;
int retval;
int i = 0;
read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);
for (;i < count ;i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;
out:
return retval;
}
static int test_ioctl_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "%s call.\n", __func__);
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}
return 0;
}
static int test_ioctl_open(struct inode *inode, struct file *filp)
{
struct test_ioctl_data *ioctl_data;
printk(KERN_ALERT "%s call.\n", __func__);
ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
if (ioctl_data == NULL)
return -ENOMEM;
rwlock_init(&ioctl_data->lock);
ioctl_data->val = 0xFF;
filp->private_data = ioctl_data;
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_ioctl_open,
.release = test_ioctl_close,
.read = test_ioctl_read,
.unlocked_ioctl = test_ioctl_ioctl,
};
static int test_ioctl_init(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
test_ioctl_major = MAJOR(dev);
cdev_init(&test_ioctl_cdev, &fops);
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_ioctl_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_ioctl_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void test_ioctl_exit(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
cdev_del(&test_ioctl_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(test_ioctl_init);
module_exit(test_ioctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_ioctl module.");
/*****************************************************************************/
那當然還少不了測試程式囉。
test.c 如下:
/*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "../test_ioctl.h"
#define DEVFILE "/dev/t1"
int main(void)
{
struct ioctl_arg cmd;
int fd;
long ret;
int num = 0;
fd = open(DEVFILE, O_RDWR);
if (fd == -1)
perror("open");
memset(&cmd, 0, sizeof(cmd));
ret = ioctl(fd, IOCTL_VALGET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("val %x\n", cmd.val);
cmd.val = 0xCC;
ret = ioctl(fd, IOCTL_VALSET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
ret = ioctl(fd, IOCTL_VALGET, &cmd);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("val %x\n", cmd.val);
num = 100;
ret = ioctl(fd, IOCTL_VALSET_NUM, num);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
ret = ioctl(fd, IOCTL_VALGET_NUM, &num);
if (ret == -1) {
printf("errno %d\n", errno);
perror("ioctl");
}
printf("num %d\n", num);
if (close(fd) != 0)
perror("close");
return 0;
}
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := test_ioctl.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
開始測試囉^^
# ls
Makefile test_code test_ioctl.c test_ioctl.h
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_ioctl/test_ioctl.o
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c: In function ‘test_ioctl_ioctl’:
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c:23:6: warning: ‘retval’ may be used uninitialized in this function
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_ioctl/test_ioctl.mod.o
LD [M] /opt/test_driver/my_driver/test_ioctl/test_ioctl.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_ioctl.ko
test_ioctl driver(major: 248) installed.
# cd test_code/
# gcc test.c -o test
# cd ..
# mknod /dev/t1 c 248 0
# ./test_code/test
val ff
val cc
num 100
# dmesg | tail
... /* 這裡略過 */
test_ioctl_open call.
IOCTL set val:cc .
test_ioctl_close call.
# rm /dev/t1
# rmmod test_ioctl
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/test_ioctl/.tmp_versions
CLEAN /opt/test_driver/my_driver/test_ioctl/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
完成 ^^
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
訂閱:
文章 (Atom)