kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

网络硬盘NFS的安装与配置

http://www.linuxidc.com/Linux/2014-11/109637.htm

NFS 是共享文件的服务的一种协议 下面给大家介绍一下这个服务器的的安装和配置。

安装

1
sudo apt-get install nfs-common nfs-kernel-server

配置

1
vim /etc/exprots

在正文的最下面输入一行

1
2
3
4
5
/srv/nfs_share *(rw)

/srv/nfs_share 表示的是我们要共享的文件目录
* 表示互联网上任何主机都可以访问 
(rw) 表示对服务器进行访问的主机可以进行的操作 也就是可读可写

如果我们只想让我们本地局域网上的主机对我们的服务器进行访问的话 可以这样写

1
/srv/nfs_share 192.168.*.*(rw)

访问

本机访问

1
sudo mount -o rw locahost:/srv/nfs_share /mnt/nfs

上面的意思是把本地的目录/srv/nfs_share 挂载到 目录/mnt/nfs上 ,这时候我们体会不到挂载点发生了变化 我们可以自己用相关的命令查询,我就不多介绍了

非本地的主机

1
sudo mount -o rw 域名:/srv/nfs_share /mnt/nfs

这个时候我们会发现NFS太简单了,所以系统管理员就麻烦了

假如在共享的目录中有我们的重要的文件,怎么办?
1
/srv/nfs_share/secret (noaccess)

就是任何主机都不能访问/srv/nfs_share/secret 这个子目录

如何限制root的权限
1
/srv/nfs_share 192.168.*。*(rw,root-aquash)
查看客户端挂载共享目录的状态
1
$ nfsstat -c
查看服务器的状态
1
$ nfsstat -s

http://stevenz.blog.hexun.com/16127677_d.html

服务器IP:172.0.0.1,主机名:p470-1, 通过NFS共享/disk1目录

在客户端使用 mount -t nfs p470-1:/disk1 /disk1 时出现

1
mount: mount to NFS server 'p470-1' failed: RPC Error: Program not registered.

错误提示。

出错原因:p470-1由于网络原因nfs服务被中断,重新开启p470-1的nfs服务然后在客户端重新mount disk1即可

service nfs restart 或 /etc/rc.d/init.d/nfs restart

VMware 'Host SMBus controller not enabled!'

https://www.centos.bz/faq/111/

Ubuntu/CentOS guest instances in VMware sometimes come up with the boot error message:

1
piix4_smbus 0000:00:007.3: Host SMBus controller not enabled!

This error is being caused because VMware doesn’t actually provide that level interface for CPU access, but Ubuntu try to load the kernel module anyway.

How to fix it:
在虚拟机中

1
sudo vim /etc/modprobe.d/blacklist.conf

add the line:

1
blacklist i2c-piix4

reboot


似乎这个错误在centos6 + 3.10* 的内核,有时kdump不起作用。

字符设备驱动和等待队列样例

前两篇的样例

字符设备驱动程序
Linux内核中的等待队列

waitqueue.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

#include <linux/socket.h>
#include <linux/tcp.h>
#include <linux/proc_fs.h>
#include <net/net_namespace.h>

#include <net/tcp.h>


static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);

struct file_operations globalvar_fops =
{
	.owner     = THIS_MODULE,
	.read = globalvar_read,
	.write = globalvar_write,
};

#define LEN 1024
static char global_var[LEN];
static int read_index = 0;
static int write_index = 0;
static spinlock_t var_lock;
static wait_queue_head_t waitq;
static int flag = 0;
static int major;

static const char procname[] = "testvar";

static int __init globalvar_init(void)
{
	init_waitqueue_head(&waitq);
	spin_lock_init(&var_lock);
//    if (!proc_net_fops_create(&init_net, procname, S_IRUSR, &globalvar_fops)) {
	if (!(major = register_chrdev(0, "globalvar", &globalvar_fops))) {
		printk("globalvar register failure\n");
		return -1;
	}
	printk("major = %d\n", major);
	return 0;
}

static void __exit globalvar_exit(void)
{
//    proc_net_remove(&init_net, procname);
	unregister_chrdev(major, "globalvar");
}

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
	int read_len;
	//等待数据可获得
	if (wait_event_interruptible(waitq, flag != 0))
		return -ERESTARTSYS;

	spin_lock(&var_lock);
	read_len = write_index - read_index;
	if (copy_to_user(buf, global_var+read_index, read_len)) {
		spin_unlock(&var_lock);
		return -EFAULT;
	}
	read_index = write_index;
	flag = 0;
	spin_unlock(&var_lock);
	return read_len;
}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
	spin_lock(&var_lock);
	if (copy_from_user(global_var+write_index, buf, len)) {
		spin_unlock(&var_lock);
		return -EFAULT;
	}
	write_index += len;
	spin_unlock(&var_lock);

	flag = 1;
	//通知数据可获得
	wake_up_interruptible(&waitq);
	return len;
}

module_init(globalvar_init);
module_exit(globalvar_exit);
MODULE_LICENSE("GPL");

Makefile

1
2
3
4
5
6
7
obj-m += waitqueue.o

PWD = $(shell pwd)
KERNEL := /lib/modules/`uname -r`/build

all:
	make -C $(KERNEL) M=$(PWD) modules
安装模块
1
insmod ./waitqueue.ko
查看对应的设备号
1
2
$ cat /proc/devices | grep globalvar
$ 249 globalvar
建立文件
1
mknod /dev/globalvar c 249 0
终端1: cat文件
1
cat /dev/globalvar
终端2: echo数据到文件
1
2
3
echo 123 > /dev/globalvar
echo 1234567 > /dev/globalvar
echo 123 > /dev/globalvar

这时就能看见终端1读到了内容。

Linux内核中的等待队列

http://blog.sina.com.cn/s/blog_49d5604e010008bn.html

等待队列可以参考net/ipv4/tcp_probe.c的实现

简单样例

Linux内核中的等待队列

Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,

如下图所示。

在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。

等待队列头
1
2
3
4
5
struct __wait_queue_head {
	wq_lock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。flags为该进程的等待标志,当前只支持互斥。

等待队列项
1
2
3
4
5
6
7
struct __wait_queue {
	unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
	struct task_struct * task;
	struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
声明和初始化
1
2
3
4
5
6
#define DECLARE_WAITQUEUE(name, tsk)            \
	wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) {    \
	task:  tsk,                                 \
	task_list: { NULL, NULL },                  \
	__WAITQUEUE_DEBUG_INIT(name)}

通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。

1
2
3
4
5
6
#define DECLARE_WAIT_QUEUE_HEAD(name)                    \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {            \
	lock:  WAITQUEUE_RW_LOCK_UNLOCKED,                   \
	task_list: { &(name).task_list, &(name).task_list }, \
	__WAITQUEUE_HEAD_DEBUG_INIT(name)}

通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空,并设置链表为"未上锁"状态。其中加入了调试相关代码。

1
static inline void init_waitqueue_head(wait_queue_head_t *q)

该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。

1
2
3
4
5
{
	q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
	INIT_LIST_HEAD(&q->task_list);
}
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

该函数初始化一个已经存在的等待队列项,它设置对应的任务结构,同时将标志位清0。

1
2
3
4
5
{
	q->flags = 0;
	q->task = p;
}
static inline int waitqueue_active(wait_queue_head_t *q)

该函数检查等待队列是否为空。

1
2
3
4
{
	return !list_empty(&q->task_list);
}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)

将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。

1
2
3
4
{
	list_add(&new->task_list, &head->task_list);
}
static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)

将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。

1
2
3
4
{
	list_add_tail(&new->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)

将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且old在head所在链表中。

1
2
3
{
	list_del(&old->task_list);
}

睡眠和唤醒操作

对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c和include/linux/sched.h中)。思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。

常用的睡眠操作有interruptible_sleep_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。

以interruptible_sleep_on为例,其展开后的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void interruptible_sleep_on(wait_queue_head_t *q)
{
	unsigned long flags;
	wait_queue_t wait;
	/* 构造当前进程对应的等待队列项 */
	init_waitqueue_entry(&wait, current);

	/* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */
	current->state = TASK_INTERRUPTIBLE;

	/* 将等待队列项添加到指定链表中 */
	wq_write_lock_irqsave(&q->lock,flags);
	__add_wait_queue(q, &wait); 
	wq_write_unlock(&q->lock);

	/* 进程重新调度,放弃执行权 */
	schedule();

	/* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
	wq_write_lock_irq(&q->lock);
	__remove_wait_queue(q, &wait);
	wq_write_unlock_irqrestore(&q->lock,flags);
	/* 至此,等待过程结束,本进程可以正常执行下面的逻辑 */
}

对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。

wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:

1
2
#define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)

wake_up函数主要是获取队列操作的锁,具体工作是调用wake_up_common完成的。

1
2
3
4
5
6
7
8
9
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
	if (q) {
	    unsigned long flags;
	    wq_read_lock_irqsave(&q->lock, flags);
	    __wake_up_common(q, mode, nr, 0);
	    wq_read_unlock_irqrestore(&q->lock, flags);
	}
}

/ The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just wake everything up. If it’s an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the non-exclusive tasks and one exclusive task. There are circumstances in which we can try to wake a task which has already started to run but is not in state TASK_RUNNING. try_to_wake_up() returns zero in this (rare) case, and we handle it by contonuing to scan the queue. /

1
static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)

参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
	struct list_head *tmp;
	struct task_struct *p;

	CHECK_MAGIC_WQHEAD(q);
	WQ_CHECK_LIST_HEAD(&q->task_list);

	/* 遍历等待队列 */
	list_for_each(tmp,&q->task_list) {
	    unsigned int state;
	    /* 获得当前等待队列项 */
	    wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

	    CHECK_MAGIC(curr->__magic);
	    /* 获得对应的进程 */
	    p = curr->task;
	    state = p->state;

	    /* 如果我们需要处理这种状态的进程 */
	    if (state & mode) {
	        WQ_NOTE_WAKER(curr);
	        if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
	            break;
	    }
	}
}

/ 唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的(except when the actual re-schedule is in progress),and as such you’re allowed to do the simpler “current->state = TASK_RUNNING” to mark yourself runnable without the overhead of this. /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline int try_to_wake_up(struct task_struct * p, int synchronous)
{
	unsigned long flags;
	int success = 0;

	/* 由于我们需要操作运行队列,必须获得对应的锁 */
	spin_lock_irqsave(&runqueue_lock, flags);
	/* 将进程状态设置为TASK_RUNNING */
	p->state = TASK_RUNNING;
	/* 如果进程已经在运行队列中,释放锁退出 */
	if (task_on_runqueue(p))
	    goto out;
	/* 否则将进程添加到运行队列中 */
	add_to_runqueue(p);

	/* 如果设置了同步标志 */
	if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
	    reschedule_idle(p);
	/* 唤醒成功,释放锁退出 */
	success = 1;
out:
	spin_unlock_irqrestore(&runqueue_lock, flags);
	return success;
}

等待队列应用模式

等待队列的的应用涉及两个进程,假设为A和B。A是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。

1
DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /* 全局变量 */

在进程A中,执行逻辑如下:

1
2
3
4
while (resource is unavaiable) {
	interruptible_sleep_on( &wq );
}
consume_resource();

在进程B中,执行逻辑如下:

1
2
produce_resource();
wake_up_interruptible( &wq );

字符设备驱动程序

http://techlife.blog.51cto.com/212583/39225

简单样例

实现如下的功能:
-字符设备驱动程序的结构及驱动程序需要实现的系统调用
-可以使用cat命令或者自编的readtest命令读出"设备"里的内容
-以8139网卡为例,演示了I/O端口和I/O内存的使用

本文中的大部分内容在Linux Device Driver这本书中都可以找到,这本书是Linux驱动开发者的唯一圣经。


先来看看整个驱动程序的入口,是char8139_init()这个函数,如果不指定MODULE_LICENSE(“GPL”), 在模块插入内核的时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的"GPL"属性。

1
2
3
4
5
6
module_init(char8139_init);
module_exit(char8139_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ypixunil");
MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC");

接着往下看char8139_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int __init char8139_init(void)
{
	int result;

	PDBG("hello. init.\n");

	/* register our char device */
	result = register_chrdev(char8139_major, "char8139", &char8139_fops);
	if (result < 0) {
		PDBG("Cannot allocate major device number!\n");
		return result;
	}
	/* register_chrdev() will assign a major device number and return if it called
	 * with "major" parameter set to 0 */
	if(char8139_major == 0)
		char8139_major=result;

	/* allocate some kernel memory we need */
	buffer = (unsigned char*)(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL));
	if (!buffer) {
		PDBG("Cannot allocate memory!\n");
		result = -ENOMEM;
		goto init_fail;
	}
	memset(buffer, 0, CHAR8139_BUFFER_SIZE);
	p_buf = buffer;

	return 0; /* everything's ok */

init_fail:
	char8139_exit();
	return result;
}

这个函数首先的工作就是使用register_chrdev()注册我们的设备的主设备号和系统调用。系统调用对于字符设备驱动程序来说就是file_operations接口。

我们先来看看char8139_major的定义,

1
2
3
4
#define DEFAULT_MAJOR 145         /* data structure used by our driver */
int char8139_major=DEFAULT_MAJOR; /* major device number. if initial value is 0,
				   * the kernel will dynamically assign a major device
				   * number in register_chrdev() */

这里我们指定我们的设备的主设备号是145,你必须找到一个系统中没有用的主设备号,可以通过"cat /proc/devices"命令来查看系统中已经使用的主设备号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[michael@char8139]$ cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
5 cua
7 vcs
10 misc
14 sound
116 alsa
128 ptm
136 pts
162 raw
180 usb
195 nvidia
226 drm

Block devices:
2 fd
3 ide0
22 ide1
[michael@char8139]$

可见在我的系统中,145还没有被使用。

指定主设备号值得考虑。像上面这样指定一个主设备号显然缺乏灵活性,而且不能保证一个驱动程序在所有的机器上都能用。可以在调用register_chrdev()时将第一个参数,即主设备号指定为0,这样register_chrdev()会分配一个空闲的主设备号作为返回值。 但是这样也有问题,我们只有在将模块插入内核之后才能得到我们设备的主设备号(使用 “cat /proc/devices”),但是要操作设备需要在系统/dev目录下建立设备结点,而建立结点时要指定主设备号。当然,你可以写一个脚本来自动完成这些事情。

总之,作为一个演示,我们还是指定主设备号为145,这样我们可以在/dev/目录下建立几个设备节点。

1
2
3
4
[root@char8139]$ mknod /dev/char8139_0 c 145 0
[root@char8139]$ mknod /dev/char8139_0 c 145 17
[root@char8139]$ mknod /dev/char8139_0 c 145 36
[root@char8139]$ mknod /dev/char8139_0 c 145 145

看一下我们建立的节点

1
2
3
4
5
6
[michael@char8139]$ ll /dev/char8139*
crw-r--r-- 1 root root 145, 0 2004-12-26 20:33 /dev/char8139_0
crw-r--r-- 1 root root 145, 17 2004-12-26 20:34 /dev/char8139_1
crw-r--r-- 1 root root 145, 36 2004-12-26 20:34 /dev/char8139_2
crw-r--r-- 1 root root 145, 145 2004-12-26 20:34 /dev/char8139_3
[michael@char8139]$

我们建立了四个节点,使用了四个次设备号,后面我们会说明次设备号的作用。

再来看看我们的file_operations的定义。这里其实只实现了read(),open(),release()三个系统调用,ioctl()只是简单返回。更有write()等函数甚至根本没有声明,没有声明的函数系统可能会调用默认的操作。

1
2
3
4
5
6
7
8
struct file_operations char8139_fops =
{
	owner: THIS_MODULE,
	read: char8139_read,
	ioctl: char8139_ioctl,
	open: char8139_open,
	release: char8139_release,
};

file_operations是每个字符设备驱动程序必须实现的系统调用,当用户对/dev中我们的设备对应结点进行操作时,linux就会调用我们驱动程序中提供的系统调用。比如用户敲入"cat /dev/char8139_0"命令,想想cat这个应用程序的实现,首先它肯定调用C语言库里的open()函数去打开/dev/char8139_0这个文件,到了系统这一层,系统会看到/dev/char8139_0不是普通磁盘文件,而是一个代表字符设备的节点,所以系统会根据/dev/char8139_0的主设备号来查找是不是已经有驱动程序使用这个相同的主设备号进行了注册,如果有,就调用驱动程序的open()实现。

为什么要这样干?因为要提供抽象,提供统一的接口,别忘了操作系统的作用之一就是这个。因为我们的设备提供的统一的接口,所以cat这个应用程序使用一般的文件操作就能从我们的设备中读出数据, 而且more, less这些应用程序都能从我们的设备中读出数据。

现在来看看我们的设备

1
2
3
4
#define CHAR8139_BUFFER_SIZE 2000
unsigned char *buffer=NULL; /* driver data buffer */
unsigned char *p_buf;
unsigned int data_size=0;

我们的设备很简单,一个2000字节的缓冲区, data_size指定缓冲区中有效数据的字节数。我们的设备只支持读不支持写。我们在char8139_init()中为缓冲区分配空间。

char8139_exit()里面的操作就是char8139_init()里面操作的反向操作。

现在我们来看看,假如用户调用了"cat /dev/char8139_3"这个命令会发生什么事情。

根据前面的介绍,我们驱动程序中的open()函数会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int char8139_open(struct inode *node, struct file *flip)
{
	int type = MINOR(node->i_rdev)>>4;
	int num = MINOR(node->i_rdev) & 0x0F;

	/* put some char in buffer to reflect the minor device number */
	*buffer=(unsigned char)('0');
	*(buffer+1)=(unsigned char)('x');
	*(buffer+2)=(unsigned char)('0'+type);
	*(buffer+3)=(unsigned char)('0'+num);
	*(buffer+4)=(unsigned char)('\n');
	data_size+=5;

	PDBG("Ok. Find treasure! 8139 I/O port base: %x\n", detect_8139_io_port());
	PDBG("OK. Find treasure! 8139 I/O memory base address: %lx\n",
	detect_8139_io_mem());

	MOD_INC_USE_COUNT;

	return 0;
}

这里演示了次设备号的作用,它让我们知道用户操作的是哪一个"次设备",是/dev/char8139_0还是/dev/char8139_3,因为对不同的"次设备",具体的操作方法可能是不一样的,这样就为一个驱动程序控制多个类似的设备提供了可能。

我们根据次设备号的不同,在buffer中填入不同的字符(次设备号的16进制表示)。

接着驱动程序中的read()函数会被调用,因为cat程序的实现就是读取文件中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ssize_t char8139_read (struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	ssize_t ret=0;

	PDBG("copy to user. count=%d, f_pos=%ld\n", (int)count, (long)*f_pos);
	if (*f_pos>= data_size)
		return ret;
	if (*f_pos + count > data_size)
		count = data_size-*f_pos;
	if (copy_to_user(buf, p_buf, count))
	{
		PDBG("OOps, copy to user error.\n");
		return -EFAULT;
	}

	p_buf += count;
	*f_pos += count;
	ret = count;

	return ret;
}

要正确的实现一个read()调用,你得想一想一个应用程序是如何调用read()从文件中读取数据的。如果你想明白了就很简单,驱动程序所要做的就是把恰当的数据传递给应用程序,这是使用copy_to_user()函数完成的。

另外,我们必须得意识到,这里只是一个很简单的演示。还有很多复杂的问题有待考虑,比如两个应用程序可能同时打开我们设备,我们的设备应该怎样反应(这取决于具体的设备应有的行为),还有互斥的问题。

然后我们看看I/O端口和I/O内存的操作。这里使用8139网卡作为一个硬件实例来演示I/O端口和I/O内存的操作。没有什么特别的,都是标准的步骤。在使用时需要注意,如果你的系统中已经有8139网卡的驱动程序,必须先关掉网络设备,卸载驱动,然后再使用本驱动程序。

使用程序包的步骤:(在我的Debian系统上如此,你的可能不同)
1. 解压
2. 编译(/usr/src/linux处必须要有内核源代码)
3. ifconfig eth0 down 关掉网络设备
rmmod 8139too 卸载原来的8139网卡驱动
insmod char8139.o 插入我们的模块
(insmod会出错, 如果你现在运行的linux版本不是你编译本驱动程序时使用的内核源代码的版本,insmod时会报告模块版本与内核版本不一致。这时,你得看看内核源代码中/include/linux/version.h文件,这个文件中的UTS_RELEASE定义了内核的版本号,你可以在驱动程序中预先定义这个宏为当前运行的内核的版本号,这样就能避免上述错误。)
4. mknode(见本文前述)
5. 试试我们的设备
./readtest
或者
cat /dev/char8139_0或
cat /dev/char8139_1或
cat /dev/char8139_2或
cat /dev/char8139_3
6. 恢复系统
rmmod char8139
modprobe 8139too
ifconfig eth0 up
如果你使用dhcp可能还需要运行dhclient