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驅動程式設計由平田豐著的這本書。
2012年1月6日 星期五
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驅動程式設計由平田豐著的這本書。
2012年1月2日 星期一
基礎 Linux Device Driver 驅動程式#8 (character device driver基礎_chrdev_sys)
前面介紹了那麼多的範例,但您有發現嗎?
所有範例程式的裝置檔,都必須要手動建立,難道不能自動建立嗎?
有,就是 udev,那您會說, udev 是什麼?
簡單,上wiki去查一下就有啦。
它是 Linux kernel 2.6系列的裝置管理器。
更詳細的說明,去wiki看囉,這裡就不多說了。
您也可以去看您的udevd是否有在運行。
# ps aux | grep udevd
root 315 0.0 0.0 2880 1132 ? S<s 09:35 0:00 udevd --daemon
這就是啦。
當驅動程式載入時,udevd daemon會偵測到這個事件,而後去檢查/sys目錄,
如果驅動程式建立了dev檔案的話,檔案裡會含有major及minor number,如此 udevd就能以它
建立裝置檔囉。
想讓驅動程式支援 udev 的話,必須豋錄驅動程式的 class 並在 /sys/class目錄下建立驅動程式資訊。
豋記class時是用 class_creat()這個kernel 函式。
它的prototype如下:
struct class *class_create(struct module *owner, const char *name);
owner: 還記得嗎? 就是 THIS_MODULE 啊
name: 就放 DRIVER_NAME 即可。
刪除豋記的 class是用 class_destroy()
void class_destroy (struct class *cls);
接著要建立 /sys/class/class 名稱/裝置名稱 這個檔案,用的是 device_create()
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
cls: 傳入由class_creat() 傳回的 class。
parent: 是指定上層 class的時候使用的,傳入 NULL也行。
devt: 是dev檔顯示的major/minor number,也可用 MKDEV巨集指定。
drvdate: 是添加到裝置的資料,傳入 NULL也行。
fmt: 裝置檔的名稱。
2.6 要舊的核心,是用class_device_create()
如要刪除豋記的裝置,可用 device_destroy()
void device_destroy (struct class *class, dev_t devt);
2.6 要舊的核心,是用class_device_destroy()
大致上了解了之後,再來就是快樂的coding 時間囉^^
不好意思啊,又要等我一下啦。
時間一分一秒的過去............
好了,
chrdev_sys.c 程式碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#define DRIVER_NAME "chrdev_sys"
static unsigned int chrdev_sys_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev chrdev_sys_cdev;
static struct class *chrdev_sys_class = NULL;
static int chrdev_sys_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_close.\n");
return 0;
}
static int chrdev_sys_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_open.\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_sys_open,
.release = chrdev_sys_close,
};
static int chrdev_sys_init(void)
{
dev_t dev = MKDEV(chrdev_sys_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;
chrdev_sys_major = MAJOR(dev);
cdev_init(&chrdev_sys_cdev, &fops);
cdev_ret = cdev_add(&chrdev_sys_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
chrdev_sys_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(chrdev_sys_class))
goto error;
device_create(chrdev_sys_class,
NULL,
MKDEV(chrdev_sys_major, 0),
NULL,
"ch_sys0");
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_sys_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_sys_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_sys_exit(void)
{
dev_t dev = MKDEV(chrdev_sys_major, 0);
device_destroy(chrdev_sys_class, dev);
class_destroy(chrdev_sys_class);
cdev_del(&chrdev_sys_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_sys_init);
module_exit(chrdev_sys_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_sys module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_sys.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
run 一下囉^^
# ls
chrdev_sys.c Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_sys modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./chrdev_sys.ko
chrdev_sys driver(major number 250) installed.
看一下 /dev/底下是否已有產生了呢?
# ls -l /dev/ch_sys0
crw------- 1 root root 250, 0 2012-01-02 17:16 /dev/ch_sys0
沒錯吧,哈哈^^
移除看看囉。
# rmmod chrdev_sys
chrdev_sys driver removed.
# ls -l /dev/ch_sys0
ls: 無法存取 /dev/ch_sys0: 沒有此一檔案或目錄
看到了嗎,不見了。
好玩吧......
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
所有範例程式的裝置檔,都必須要手動建立,難道不能自動建立嗎?
有,就是 udev,那您會說, udev 是什麼?
簡單,上wiki去查一下就有啦。
它是 Linux kernel 2.6系列的裝置管理器。
更詳細的說明,去wiki看囉,這裡就不多說了。
您也可以去看您的udevd是否有在運行。
# ps aux | grep udevd
root 315 0.0 0.0 2880 1132 ? S<s 09:35 0:00 udevd --daemon
這就是啦。
當驅動程式載入時,udevd daemon會偵測到這個事件,而後去檢查/sys目錄,
如果驅動程式建立了dev檔案的話,檔案裡會含有major及minor number,如此 udevd就能以它
建立裝置檔囉。
想讓驅動程式支援 udev 的話,必須豋錄驅動程式的 class 並在 /sys/class目錄下建立驅動程式資訊。
豋記class時是用 class_creat()這個kernel 函式。
它的prototype如下:
struct class *class_create(struct module *owner, const char *name);
owner: 還記得嗎? 就是 THIS_MODULE 啊
name: 就放 DRIVER_NAME 即可。
刪除豋記的 class是用 class_destroy()
void class_destroy (struct class *cls);
接著要建立 /sys/class/class 名稱/裝置名稱 這個檔案,用的是 device_create()
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
cls: 傳入由class_creat() 傳回的 class。
parent: 是指定上層 class的時候使用的,傳入 NULL也行。
devt: 是dev檔顯示的major/minor number,也可用 MKDEV巨集指定。
drvdate: 是添加到裝置的資料,傳入 NULL也行。
fmt: 裝置檔的名稱。
2.6 要舊的核心,是用class_device_create()
如要刪除豋記的裝置,可用 device_destroy()
void device_destroy (struct class *class, dev_t devt);
2.6 要舊的核心,是用class_device_destroy()
大致上了解了之後,再來就是快樂的coding 時間囉^^
不好意思啊,又要等我一下啦。
時間一分一秒的過去............
好了,
chrdev_sys.c 程式碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#define DRIVER_NAME "chrdev_sys"
static unsigned int chrdev_sys_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev chrdev_sys_cdev;
static struct class *chrdev_sys_class = NULL;
static int chrdev_sys_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_close.\n");
return 0;
}
static int chrdev_sys_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_open.\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_sys_open,
.release = chrdev_sys_close,
};
static int chrdev_sys_init(void)
{
dev_t dev = MKDEV(chrdev_sys_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;
chrdev_sys_major = MAJOR(dev);
cdev_init(&chrdev_sys_cdev, &fops);
cdev_ret = cdev_add(&chrdev_sys_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
chrdev_sys_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(chrdev_sys_class))
goto error;
device_create(chrdev_sys_class,
NULL,
MKDEV(chrdev_sys_major, 0),
NULL,
"ch_sys0");
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_sys_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_sys_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_sys_exit(void)
{
dev_t dev = MKDEV(chrdev_sys_major, 0);
device_destroy(chrdev_sys_class, dev);
class_destroy(chrdev_sys_class);
cdev_del(&chrdev_sys_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_sys_init);
module_exit(chrdev_sys_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_sys module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_sys.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
run 一下囉^^
# ls
chrdev_sys.c Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_sys modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./chrdev_sys.ko
chrdev_sys driver(major number 250) installed.
看一下 /dev/底下是否已有產生了呢?
# ls -l /dev/ch_sys0
crw------- 1 root root 250, 0 2012-01-02 17:16 /dev/ch_sys0
沒錯吧,哈哈^^
移除看看囉。
# rmmod chrdev_sys
chrdev_sys driver removed.
# ls -l /dev/ch_sys0
ls: 無法存取 /dev/ch_sys0: 沒有此一檔案或目錄
看到了嗎,不見了。
好玩吧......
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
2012年1月1日 星期日
基礎 Linux Device Driver 驅動程式#7 (character device driver基礎_minor number)
有時候,如果有多個裝置,但想要做出不同的行為,那怎麼辦呢?
之前有介紹過 minor 吧。
minor number得由驅動程式自已去管理,如果想要不同的 minor提供不同的功能的話,
可以在當開啟裝置時,做minor的判斷。
但,如何做呢?不用擔心,接下來,我將會為各位示範實作的方法。
又是來寫code的時候啦,等我囉^^
時間又在一點一滴的流逝....
......................
不好意思,老婆大人在催了,
如有不足的話,我會再check。
chrdev_minor.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "chrdev_minor"
static unsigned int chrdev_minor_major = 0;
static unsigned int num_of_dev = 2;
static struct cdev chrdev_minor_cdev;
ssize_t one_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
ssize_t one_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
static int one_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
static int one_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
struct file_operations one_fops = {
.open = one_open,
.release = one_close,
.read = one_read,
.write = one_write,
};
ssize_t zero_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
ssize_t zero_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
static int zero_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
static int zero_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
struct file_operations zero_fops = {
.open = zero_open,
.release = zero_close,
.read = zero_read,
.write = zero_write,
};
static int chrdev_minor_open(struct inode *inode, struct file *filp)
{
switch (iminor(inode)) {
case 0:
filp->f_op = &zero_fops;
break;
case 1:
filp->f_op = &one_fops;
break;
default:
return -ENXIO;
}
if (filp->f_op && filp->f_op->open)
return filp->f_op->open(inode, filp);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_minor_open,
};
static int chrdev_minor_init(void)
{
dev_t dev = MKDEV(chrdev_minor_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;
chrdev_minor_major = MAJOR(dev);
cdev_init(&chrdev_minor_cdev, &fops);
cdev_ret = cdev_add(&chrdev_minor_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_minor_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_minor_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_minor_exit(void)
{
dev_t dev = MKDEV(chrdev_minor_major, 0);
cdev_del(&chrdev_minor_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_minor_init);
module_exit(chrdev_minor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_minor module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_minor.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
# ls
chrdev_minor.c Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_minor modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.o
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:44:2: warning: initialization from incompatible pointer type
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:75:2: warning: initialization from incompatible pointer type
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_minor.c chrdev_minor.mod.c chrdev_minor.o modules.order
chrdev_minor.ko chrdev_minor.mod.o Makefile Module.symvers
# insmod ./chrdev_minor.ko
chrdev_minor driver(major number 250) installed.
# mknod /dev/ch_m1 c 250 0
# mknod /dev/ch_m2 c 250 1
# ls -l /dev/ch_m*
crw-r--r-- 1 root root 250, 0 2012-01-01 20:38 /dev/ch_m1
crw-r--r-- 1 root root 250, 1 2012-01-01 20:38 /dev/ch_m2
# cat /dev/ch_m1
# dmesg | tail
.......... /* 以上略過 */
This is zero_open (minor=0)
zero_read called
This is zero_close (minor=0)
# cat /dev/ch_m2
# dmesg | tail
.......... /* 以上略過 */
This is one_open (minor=1)
one_read called
This is one_close (minor=1)
各位,真不好意思囉,接下來的教學,未完待續.....
之前有介紹過 minor 吧。
minor number得由驅動程式自已去管理,如果想要不同的 minor提供不同的功能的話,
可以在當開啟裝置時,做minor的判斷。
但,如何做呢?不用擔心,接下來,我將會為各位示範實作的方法。
又是來寫code的時候啦,等我囉^^
時間又在一點一滴的流逝....
......................
不好意思,老婆大人在催了,
如有不足的話,我會再check。
chrdev_minor.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "chrdev_minor"
static unsigned int chrdev_minor_major = 0;
static unsigned int num_of_dev = 2;
static struct cdev chrdev_minor_cdev;
ssize_t one_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
ssize_t one_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
static int one_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
static int one_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
struct file_operations one_fops = {
.open = one_open,
.release = one_close,
.read = one_read,
.write = one_write,
};
ssize_t zero_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
ssize_t zero_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}
static int zero_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
static int zero_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}
struct file_operations zero_fops = {
.open = zero_open,
.release = zero_close,
.read = zero_read,
.write = zero_write,
};
static int chrdev_minor_open(struct inode *inode, struct file *filp)
{
switch (iminor(inode)) {
case 0:
filp->f_op = &zero_fops;
break;
case 1:
filp->f_op = &one_fops;
break;
default:
return -ENXIO;
}
if (filp->f_op && filp->f_op->open)
return filp->f_op->open(inode, filp);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_minor_open,
};
static int chrdev_minor_init(void)
{
dev_t dev = MKDEV(chrdev_minor_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;
chrdev_minor_major = MAJOR(dev);
cdev_init(&chrdev_minor_cdev, &fops);
cdev_ret = cdev_add(&chrdev_minor_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_minor_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_minor_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_minor_exit(void)
{
dev_t dev = MKDEV(chrdev_minor_major, 0);
cdev_del(&chrdev_minor_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_minor_init);
module_exit(chrdev_minor_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_minor module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_minor.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
# ls
chrdev_minor.c Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_minor modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.o
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:44:2: warning: initialization from incompatible pointer type
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:75:2: warning: initialization from incompatible pointer type
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_minor.c chrdev_minor.mod.c chrdev_minor.o modules.order
chrdev_minor.ko chrdev_minor.mod.o Makefile Module.symvers
# insmod ./chrdev_minor.ko
chrdev_minor driver(major number 250) installed.
# mknod /dev/ch_m1 c 250 0
# mknod /dev/ch_m2 c 250 1
# ls -l /dev/ch_m*
crw-r--r-- 1 root root 250, 0 2012-01-01 20:38 /dev/ch_m1
crw-r--r-- 1 root root 250, 1 2012-01-01 20:38 /dev/ch_m2
# cat /dev/ch_m1
# dmesg | tail
.......... /* 以上略過 */
This is zero_open (minor=0)
zero_read called
This is zero_close (minor=0)
# cat /dev/ch_m2
# dmesg | tail
.......... /* 以上略過 */
This is one_open (minor=1)
one_read called
This is one_close (minor=1)
各位,真不好意思囉,接下來的教學,未完待續.....
基礎 Linux Device Driver 驅動程式#6 (character device driver基礎_讀寫)
既然各位對最基本的驅動程式已有概念了,
那當然我們得再去一步的探討囉。
在上一個驅動程式,我們實作了open及release,
但,實際上只有這些handler了嗎?
當然不只,還有以下實作:
是定義在 include/include/linux/fs.h 裡。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
但隨著之後的範例會愈來愈多,非常建議各位有獨立的機器或者跑虛擬機比較保險喔^^
回到主題,既然有這麼多的 handler的實作,但並不是完全都用得到,那其它沒用到的函式呢?
最基本的驅動程式,都會實作open與release(close),其他沒實作的handler就會被定義成NULL。
像open 這個handler,也就是user process的程式在操作裝置檔做的開啟動作,
結束當然就是release囉。
而其中的 inode 引數,是一個內含inode資訊的結構指標,有幾個成員我們會用到的,
bdev_t i_rdev Major/Minor Number
void *i_private 驅動程式私有指標
既然如此,我們就可以透過 i_rdev這個成員去取得 major及minor number了。
怎麼做呢? Linux已提供了以下方法提供實作:
unsigned int iminor(const struct inode *inode);
unsigned int imajor(const struct inode *inode);
i_private 成員則是驅動程式可以自由使用的指標,不設定也沒關係。
file 也是很大的一個結構,以下是常用的幾個成員:
struct file_operations *fops 系統呼叫 handlers
unsigned int f_flags open函式第二個引數傳入的旗標
void *private_data 驅動程式私有資料指標
通常 fops 不需修改,但有時假設需要對不同裝置提供不同的處理的話,就可自已更新fops的成員。
open handler傳回值 0為成功,非0為失敗。
在上篇教學,我們只示範了open與release這兩個 handler,再來,我們將實作read及write 這兩個handler。
ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
read handler 是指 當user process的程式想要向驅動程式讀取資料時用的。
file 這個結構指標是指向開啟裝置檔 kernel建立的file 結構,和open收到的指標是同一個。
因此,當open handler設給 filp->private成員的指標,在read handler也能拿來用。
buf引數是 user process呼叫read()時指定的緩衝區指標,但驅動程式不能直接取用buf指標,
必須透過 copy_to_user 這個kernel提供的函式將資料複製過去。
count引數是 user process呼叫read()時提供的緩衝區空間。
f_pos引數是offset。
read handler傳回值 0為什麼都沒做,正為寫入緩衝區的byte數,負為發生錯誤。
ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
write handler和read handler差不多,主要差在,write handler是當user process要傳資料給驅動時用到的,
所以 buf引數就必須用 copy_from_user 這個kernel提供的函式將資料從緩衝區讀入。
read handler傳回值 0為什麼都沒做,正為從緩衝區讀入的byte數,負為發生錯誤。
相信各位已對read 與write這兩個 handler有了初步的了解,就來試一下囉^^
給我點時間,寫一下code喔。
時間一點一滴的流失.....
....................
好囉,sorry, 久等了,那我們就來run一下囉^^
chrdev_rw.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "chrdev_rw"
static unsigned int num_of_dev = 1;
static unsigned int chrdev_rw_major = 0;
static struct cdev chrdev_rw_cdev;
struct chrdev_rw_data {
unsigned char val;
rwlock_t lock;
};
static int chrdev_rw_open(struct inode *inode, struct file *filp)
{
struct chrdev_rw_data *data_p;
data_p = (struct chrdev_rw_data *)kmalloc(sizeof(struct chrdev_rw_data), GFP_KERNEL);
if (data_p == NULL) {
printk(KERN_ALERT "malloc error!!!\n");
return -ENOMEM;
}
data_p->val = 0xff;
rwlock_init(&data_p->lock);
filp->private_data = data_p;
return 0;
}
static int chrdev_rw_close(struct inode *inode, struct file *filp)
{
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}
return 0;
}
ssize_t chrdev_rw_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
int i;
printk(KERN_ALERT "%s\n", __func__);
read_lock(&data_p->lock);
val = data_p->val;
read_unlock(&data_p->lock);
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
val_ret = -EFAULT;
goto out;
}
}
val_ret = count;
out:
return val_ret;
}
ssize_t chrdev_rw_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
printk(KERN_ALERT "%s\n", __func__);
if (count >= 1) {
if (copy_from_user(&val, &buf[0], 1)) {
val_ret = -EFAULT;
goto out;
}
}
write_lock(&data_p->lock);
data_p->val = val;
write_unlock(&data_p->lock);
out:
return val_ret;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_rw_open,
.release = chrdev_rw_close,
.read = chrdev_rw_read,
.write = chrdev_rw_write,
};
static int chrdev_rw_init(void)
{
dev_t dev = MKDEV(chrdev_rw_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;
chrdev_rw_major = MAJOR(dev);
cdev_init(&chrdev_rw_cdev, &fops);
cdev_ret = cdev_add(&chrdev_rw_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_rw_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_rw_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_rw_exit(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);
cdev_del(&chrdev_rw_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_rw_init);
module_exit(chrdev_rw_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_rw module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_rw.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
有驅動程式原始碼和Makefile,當然也少不了測試程式囉,如下:
test_rw.c
/*****************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#define DEV_FILE "/dev/ch_rw"
int main(void)
{
int fd;
unsigned char buf;
ssize_t ret;
fd = open(DEV_FILE, O_RDWR);
if (fd == -1)
perror("open");
read(fd, &buf, 1);
printf("Before write, char is %x\n", buf);
buf = 0xCC;
ret = write(fd, &buf, 1);
if (ret <= 0)
perror("write");
read(fd, &buf, 1);
printf("After write, char is %x\n", buf);
if (close(fd) != 0)
perror("close");
return 0;
}
/*****************************************************************************/
那我們就來run一下囉。
# ls
chrdev_rw.c Makefile test_code
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_rw modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_rw.c chrdev_rw.ko chrdev_rw.mod.c chrdev_rw.mod.o chrdev_rw.o Makefile modules.order Module.symvers test_code
# cd test_code/
# ls
test_rw.c
# gcc test_rw.c -o test_rw
# ls
test_rw test_rw.c
OK, 模組和測試程式都編好了後,就來測試一下。
# cd ..
# insmod ./chrdev_rw.ko
chrdev_rw driver(major number 250) installed.
# mknod /dev/ch_rw c 250 0
# cd test_code/
# ./test_rw
Before write, char is ff
write: Success
After write, char is cc
成功~~~~~
我們目前是用 read_lock 和 write_lock做資料讀寫的鎖定,
但實際上還有很多種方式,到時,在後面的教學,我將會為各位說明更多種的實作方法。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
那當然我們得再去一步的探討囉。
在上一個驅動程式,我們實作了open及release,
但,實際上只有這些handler了嗎?
當然不只,還有以下實作:
是定義在 include/include/linux/fs.h 裡。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
但隨著之後的範例會愈來愈多,非常建議各位有獨立的機器或者跑虛擬機比較保險喔^^
回到主題,既然有這麼多的 handler的實作,但並不是完全都用得到,那其它沒用到的函式呢?
最基本的驅動程式,都會實作open與release(close),其他沒實作的handler就會被定義成NULL。
像open 這個handler,也就是user process的程式在操作裝置檔做的開啟動作,
結束當然就是release囉。
而其中的 inode 引數,是一個內含inode資訊的結構指標,有幾個成員我們會用到的,
bdev_t i_rdev Major/Minor Number
void *i_private 驅動程式私有指標
既然如此,我們就可以透過 i_rdev這個成員去取得 major及minor number了。
怎麼做呢? Linux已提供了以下方法提供實作:
unsigned int iminor(const struct inode *inode);
unsigned int imajor(const struct inode *inode);
i_private 成員則是驅動程式可以自由使用的指標,不設定也沒關係。
file 也是很大的一個結構,以下是常用的幾個成員:
struct file_operations *fops 系統呼叫 handlers
unsigned int f_flags open函式第二個引數傳入的旗標
void *private_data 驅動程式私有資料指標
通常 fops 不需修改,但有時假設需要對不同裝置提供不同的處理的話,就可自已更新fops的成員。
open handler傳回值 0為成功,非0為失敗。
在上篇教學,我們只示範了open與release這兩個 handler,再來,我們將實作read及write 這兩個handler。
ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
read handler 是指 當user process的程式想要向驅動程式讀取資料時用的。
file 這個結構指標是指向開啟裝置檔 kernel建立的file 結構,和open收到的指標是同一個。
因此,當open handler設給 filp->private成員的指標,在read handler也能拿來用。
buf引數是 user process呼叫read()時指定的緩衝區指標,但驅動程式不能直接取用buf指標,
必須透過 copy_to_user 這個kernel提供的函式將資料複製過去。
count引數是 user process呼叫read()時提供的緩衝區空間。
f_pos引數是offset。
read handler傳回值 0為什麼都沒做,正為寫入緩衝區的byte數,負為發生錯誤。
ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
write handler和read handler差不多,主要差在,write handler是當user process要傳資料給驅動時用到的,
所以 buf引數就必須用 copy_from_user 這個kernel提供的函式將資料從緩衝區讀入。
read handler傳回值 0為什麼都沒做,正為從緩衝區讀入的byte數,負為發生錯誤。
相信各位已對read 與write這兩個 handler有了初步的了解,就來試一下囉^^
給我點時間,寫一下code喔。
時間一點一滴的流失.....
....................
好囉,sorry, 久等了,那我們就來run一下囉^^
chrdev_rw.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "chrdev_rw"
static unsigned int num_of_dev = 1;
static unsigned int chrdev_rw_major = 0;
static struct cdev chrdev_rw_cdev;
struct chrdev_rw_data {
unsigned char val;
rwlock_t lock;
};
static int chrdev_rw_open(struct inode *inode, struct file *filp)
{
struct chrdev_rw_data *data_p;
data_p = (struct chrdev_rw_data *)kmalloc(sizeof(struct chrdev_rw_data), GFP_KERNEL);
if (data_p == NULL) {
printk(KERN_ALERT "malloc error!!!\n");
return -ENOMEM;
}
data_p->val = 0xff;
rwlock_init(&data_p->lock);
filp->private_data = data_p;
return 0;
}
static int chrdev_rw_close(struct inode *inode, struct file *filp)
{
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}
return 0;
}
ssize_t chrdev_rw_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
int i;
printk(KERN_ALERT "%s\n", __func__);
read_lock(&data_p->lock);
val = data_p->val;
read_unlock(&data_p->lock);
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
val_ret = -EFAULT;
goto out;
}
}
val_ret = count;
out:
return val_ret;
}
ssize_t chrdev_rw_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
printk(KERN_ALERT "%s\n", __func__);
if (count >= 1) {
if (copy_from_user(&val, &buf[0], 1)) {
val_ret = -EFAULT;
goto out;
}
}
write_lock(&data_p->lock);
data_p->val = val;
write_unlock(&data_p->lock);
out:
return val_ret;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_rw_open,
.release = chrdev_rw_close,
.read = chrdev_rw_read,
.write = chrdev_rw_write,
};
static int chrdev_rw_init(void)
{
dev_t dev = MKDEV(chrdev_rw_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;
chrdev_rw_major = MAJOR(dev);
cdev_init(&chrdev_rw_cdev, &fops);
cdev_ret = cdev_add(&chrdev_rw_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_rw_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_rw_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}
static void chrdev_rw_exit(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);
cdev_del(&chrdev_rw_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(chrdev_rw_init);
module_exit(chrdev_rw_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_rw module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)
obj-m := chrdev_rw.o
all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
有驅動程式原始碼和Makefile,當然也少不了測試程式囉,如下:
test_rw.c
/*****************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#define DEV_FILE "/dev/ch_rw"
int main(void)
{
int fd;
unsigned char buf;
ssize_t ret;
fd = open(DEV_FILE, O_RDWR);
if (fd == -1)
perror("open");
read(fd, &buf, 1);
printf("Before write, char is %x\n", buf);
buf = 0xCC;
ret = write(fd, &buf, 1);
if (ret <= 0)
perror("write");
read(fd, &buf, 1);
printf("After write, char is %x\n", buf);
if (close(fd) != 0)
perror("close");
return 0;
}
/*****************************************************************************/
那我們就來run一下囉。
# ls
chrdev_rw.c Makefile test_code
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_rw modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.mod.o
LD [M] /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_rw.c chrdev_rw.ko chrdev_rw.mod.c chrdev_rw.mod.o chrdev_rw.o Makefile modules.order Module.symvers test_code
# cd test_code/
# ls
test_rw.c
# gcc test_rw.c -o test_rw
# ls
test_rw test_rw.c
OK, 模組和測試程式都編好了後,就來測試一下。
# cd ..
# insmod ./chrdev_rw.ko
chrdev_rw driver(major number 250) installed.
# mknod /dev/ch_rw c 250 0
# cd test_code/
# ./test_rw
Before write, char is ff
write: Success
After write, char is cc
成功~~~~~
我們目前是用 read_lock 和 write_lock做資料讀寫的鎖定,
但實際上還有很多種方式,到時,在後面的教學,我將會為各位說明更多種的實作方法。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
訂閱:
文章 (Atom)