青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

小默

ldd3讀書筆記

Google 筆記本
ldd3讀書筆記

最近編輯過的 2011年1月3日 
Q:implicit declaration of function 'NIPQUAD'
A:


 Using NIPQUAD() with NIPQUAD_FMT, %d.%d.%d.%d or %u.%u.%u.%u
can be replaced with %pI4

-		dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n",
-			NIPQUAD(src_ipaddr),
-			NIPQUAD(arpinfo->smsk.s_addr),
-			NIPQUAD(arpinfo->src.s_addr),
+		dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n",
+			&src_ipaddr,
+			&arpinfo->smsk.s_addr,
+			&arpinfo->src.s_addr,
 			arpinfo->invflags & ARPT_INV_SRCIP ? " (INV)" : "");



網卡驅動分析

---
1 找到網卡對應驅動模塊,測試

用lshw查看網卡對應的驅動。我的是pcnet32。
 *-network
                description: Ethernet interface
                product: 79c970 [PCnet32 LANCE]
                vendor: Hynix Semiconductor (Hyundai Electronics)
                physical id: 1
                bus info: pci@0000:02:01.0
                logical name: eth1
                version: 10
                serial: 00:0c:29:b5:16:0d
                width: 32 bits
                clock: 33MHz
                capabilities: bus_master rom ethernet physical logical
                configuration: broadcast=yes driver=pcnet32 driverversion=1.35 ip=192.168.159.132 latency=64 link=yes maxlatency=255 mingnt=6 multicast=yes
                resources: irq:19 ioport:2000(size=128) memory:dc400000-dc40ffff

lsmod看到對應模塊:
$lsmod

到 /usr/src/kernels/linux-2.6.36/drivers/net 下,ls可以看到 pcnet32.c 和對應編譯好的.ko模塊。
$ ls | grep pcnet32

rmmod右上角網絡標識圖成了叉叉。
insmod pcnet32.ko 網絡標識圖重新連上。
(難道每次手動停止/啟動網絡連接,實際就是調用了卸載/加載模塊?囧)

--
2  嘗試編譯驅動
把pcnet32.c拷貝出來。
掃了眼net下面的Makefile,300行,嚇死我鳥。
然后寫了個4行的Makefile,居然編譯插入運行一些正常,囧翻了。。。

--
3  分析代碼

--
4  分析在網絡模塊堆疊中的位置,數據流向

--
5  還沒有想好。。改下代碼?
2.6.20內核以后的skbuff.h頭文件中將struct sk_buff結構體修改了,h中包含有傳輸層的報文頭,nh中包含有網絡層的報文頭,而mac中包含的是鏈路層的報文頭。
linux-2.6.20以后的內核頭文件sk_buff.h中這三個成員提取到單獨的變量對應關系如下:
h-->transport_header;
nh-->network_header;
mac-->mac_header;
struct net


以前查過一次,還有一點印象
struct net是一個網絡名字空間namespace,在不同的名字空間里面可以有自己的轉發信息庫,有自己的一套net_device等等。
默認情況下都是使用init_net這個全局變量
Linux 系統內核空間與用戶空間通信的實現與分析
https://www.ibm.com/developerworks/cn/linux/l-netlink/

推薦使用netlink套接字實現中斷環境和用戶態進程通信。

CPU運行狀態有4種:處理硬中斷;處理軟中斷;運行于內核態,但有進程上下文;運行于用戶態進程。
前三種狀態,CPU運行于內核空間。

當CPU運行進程上下文時,可以被阻塞。這時可以通過 copy_from_user()/copy_to_user() 實現內核態和用戶態數據的拷貝。

當CPU運行于硬/軟中斷環境下時,不可以被阻塞。這時有2種方法實現和用戶態的通信。
第1種方法:用一個和進程上下文相關的內核線程,完成從用戶態接收數據的任務;再通過一個臨界區將數據傳給中斷過程。缺點:中斷過程不能實時接收來自用戶態進程的數據。
第2中方法:使用netlink套接字。用一個軟中斷調用用戶實現指定的接收函數;再通過臨界區將數據傳給中斷過程。因為使用軟中斷來接收數據,所以可以保證數據接收的實時性。
Q:軟中斷不可以被阻塞,怎么從用戶空間接收數據。

netlink套接字用法:


--

IMP2

--Makefile

obj-m += imp2_k.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
make -C $(KDIR) M=$(shell pwd) modules
gcc -o imp2_u imp2_u.c
clean:
make -C $(DIR) M=$(shell pwd) clean
rm -f *odule* imp2_k

--imp2.h

#ifndef _IMP2_H_
#define _IMP2_H_

#define IMP2_U_PID 0  /*用戶空間進程ID*/
#define IMP2_K_MSG 1  /*內核發出的消息*/
#define IMP2_CLOSE 2  /*用戶空間進程關閉*/

#define NL_IMP2  31  /*自定義的netlink協議類型*/

/*nlmsghdr 后的數據*/
struct packet_info{
__u32 src;
__u32 dest;
};

#endif

--imp2_u.c

#include <unistd.h>
#include <stdio.h>
#include <linux/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <signal.h>
#include "imp2.h"

struct msg_to_kernel{
struct nlmsghdr hdr;
};

struct u_packet_info{
struct nlmsghdr hdr;
struct packet_info icmp_info;
};

static int skfd;

/*信號SIGINT的處理函數-告訴內核用戶端關閉*/
static void sig_int(int signo)
{
struct sockaddr_nl kpeer;
struct msg_to_kernel message;

/*內核端地址*/
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;

/*要傳遞的消息*/
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0);
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = IMP2_CLOSE;
message.hdr.nlmsg_pid = getpid();

/*sendto - send a message on a socket*/
sendto(skfd, /*int sockfd*/
   &message,  /*const void *buf*/
   &message.hdr.nlmsg_len,  /*size_t len*/
   0,  /*int flags*/
   (struct sockaddr *)(&kpeer), /*const struct sockaddr *dest_addr*/
   sizeof(kpeer)); /*socklen_t addrlen*/

close(skfd);

exit(0);
}

int main(void)
{
struct sockaddr_nl local;
struct sockaddr_nl kpeer;
int kpeerlen;
struct msg_to_kernel message;
struct u_packet_info info;
int sendlen = 0;
int rcvlen = 0;
struct in_addr addr;

/*創建套接字,設置用戶端地址,綁定套接字和地址*/
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
/*netlink套接字,用戶層端地址*/
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;
if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){
printf("bind() error\n");
return -1;
}

/*設置SIGINT信號的處理函數為sig_int*/
signal(SIGINT, sig_int);

/*netlink套接字,內核端地址*/
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;

/*把用戶端進程的pid傳遞給內核*/
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0);
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = IMP2_U_PID;
message.hdr.nlmsg_pid = local.nl_pid;
sendto(skfd, &message, message.hdr.nlmsg_len, 0,
(struct sockaddr *)&kpeer, sizeof(kpeer));

/*循環接收內核發來的數據*/
while(1){
kpeerlen = sizeof(struct sockaddr_nl);
rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),
0, (struct sockaddr *)&kpeer, &kpeerlen);

addr.s_addr = info.icmp_info.src;
printf("src: %s, ", inet_ntoa(addr));
addr.s_addr = info.icmp_info.dest;
printf("dest: %s\n", inet_ntoa(addr));
}

return 0;
}

-- imp2_k.c

/* 從netfilter的NF_IP_PRE_ROUTING點截獲ICMP數據報,記錄源地址和目錄地址
 * 使用netlink套接字將信息傳遞給用戶進程,再由用戶進程打印到終端上
 */
 
#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv4.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netlink.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
#include <net/sock.h>
#include "imp2.h"

DECLARE_MUTEX(receive_sem);

static struct sock *nlfd;

struct{
__u32 pid;
rwlock_t lock;
}user_proc;

/*netlink的接收回調函數,負責接收用戶層端的數據。運行在軟中斷環境*/
static void kernel_receive(struct sk_buff *_skb)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;

if(down_trylock(&receive_sem))
return;

/*檢查數據合法性*/
skb = skb_get(_skb);

if(skb->len >= sizeof(struct nlmsghdr)){
nlh = (struct nlmsghdr *)skb->data;
if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) && (skb->len >= nlh->nlmsg_len)){
/*用戶層端進程id*/
if(nlh->nlmsg_type == IMP2_U_PID){
write_lock_bh(&user_proc.lock);
user_proc.pid = nlh->nlmsg_pid;
write_unlock_bh(&user_proc.lock);
}
/*用戶端進程關閉,置pid為0*/
else if(nlh->nlmsg_type == IMP2_CLOSE){
write_lock_bh(&user_proc.lock);
if(nlh->nlmsg_pid == user_proc.pid)
user_proc.pid = 0;
write_unlock_bh(&user_proc.lock);
}
}
}
kfree_skb(skb);
up(&receive_sem);
}

/*給netlink的用戶端發送數據*/
static int send_to_user(struct packet_info *info)
{
int ret;
int size;
unsigned char *old_tail;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;

size = NLMSG_SPACE(sizeof(*info)); /*字節對齊后的數據報長度*/

/*開辟一個新的套接字緩沖*/
skb = alloc_skb(size, GFP_ATOMIC);
old_tail = skb->tail;

/*填寫數據報相關信息*/
nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));

/*將struct packet_info作為nlmsg的數據*/
packet = NLMSG_DATA(nlh);
memset(packet, 0, sizeof(struct packet_info));
packet->src = info->src;
packet->dest = info->dest;

/*計算經過字節對其后nlmsg的長度*/
nlh->nlmsg_len = skb->tail - old_tail;
NETLINK_CB(skb).dst_group = 0;

/*發送數據*/
read_lock_bh(&user_proc.lock);
ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
read_unlock_bh(&user_proc.lock);

return ret;

/*若發送失敗,則撤銷套接字*/
nlmsg_failure:
if(skb)
kfree_skb(skb);

return -1;
}

/* netfilter NF_IP_PRE_ROUTING點的掛接函數
 * 截獲ip數據報
 */
static unsigned int get_icmp(unsigned int hook,
struct sk_buff *pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph = NULL;
struct packet_info info;

iph = ip_hdr(pskb);
/*如果傳輸層協議是ICMP*/
if(iph->protocol == IPPROTO_ICMP){
read_lock_bh(&user_proc.lock);
if(user_proc.pid != 0){
read_unlock_bh(&user_proc.lock);
info.src = iph->saddr;
info.dest = iph->daddr;
send_to_user(&info); /*將數據發送給netlink的用戶層端*/
}
else
read_unlock_bh(&user_proc.lock);
}

return NF_ACCEPT;
}

/*給netfilter框架的NF_IP_PRE_ROUTING點上掛接函數get_icmp()*/
static struct nf_hook_ops imp2_ops = {
.hook = get_icmp, /*hook被調用時執行的回調函數*/
.pf = PF_INET, /*協議族,<linux/socket.h>。ipv4使用PF_INET*/
.hooknum = 0, /*安裝的這個函數,對應具體的hook類型*/
.priority = NF_IP_PRI_FILTER - 1,
};

static int __init init(void)
{
rwlock_init(&user_proc.lock);

/* 創建netlink套接字,指明接收數據的回調函數kernel_receive()
 * 這里協議NL_IMP2是自定義的*/
nlfd = netlink_kernel_create(&init_net, NL_IMP2, 0, kernel_receive, NULL, THIS_MODULE);
if(!nlfd){
printk("cannot create a netlink socket\n");
return -1;
}

/*注冊netfilter hook函數*/
return nf_register_hook(&imp2_ops);
}

static void __exit fini(void)
{
/*釋放套接字所占資源*/
if(nlfd){
sock_release(nlfd->sk_socket);
}
/*注銷netfilter hook函數*/
nf_unregister_hook(&imp2_ops);
}

module_init(init);
module_exit(fini);

at the beginning

# 一個驅動程序的角色是提供機制,而不是策略 2010-11-15 09:26 小默

“一個驅動程序的角色是提供機制,而不是策略。”--ldd3

機制:提供什么能力
策略:如何使用這些能力
機制和策略由軟件不同部分,或完全不同的軟件實現。

比如第一次實習時:
我們這邊負責寫驅動,只關注實現什么功能,怎么實現這樣功能,這是機制。我們可以直接在設備管理器中安裝卸載,或者用命令行安裝卸載使用等,隨意,也就是開發過程完全不考慮策略。
等開發進行到了一定階段,又招了另外一名同學負責界面,這是策略。用戶怎么使用這個驅動,操作界面是怎樣的,是由他來負責的。

不知道是不是這個意思O(∩_∩)O~~  回復  更多評論 刪除評論  修改評論

# makefile寫法 2010-11-15 17:14 小默

對于單個.c文件的hello world例子:
obj-m := hello.o
從目標文件hello.o簡歷一個模塊hello.ko

如果模塊來自兩個源文件file1.c和file2.c:
obj-m := module.o
module-objs := file1.o file2.o

上面的命令,必須在內核系統上下建立文中被調用

-----

在任意當前工作目錄中,需要在makefile中指明源碼樹的路徑。

ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build //TODO 不是在源碼樹中么/(ㄒoㄒ)/~~
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

創建一個模塊需要調用兩次上面的makefile
第一次:沒有設置變量KERNELRELEASE,通過已安裝模塊目錄中的符號鏈接指回內核建立樹;然后運行default中的make命令,第二次使用makefile
第二次:已經設置了變量KERNELRELEASE,直接obj-m := hello.o創建模塊  回復  更多評論 刪除評論  修改評論

# 加載和卸載模塊 2010-11-15 17:57 小默

modprobe實現和insmod一樣加載模塊到內核的功能
不同的是,加載前會檢查模塊中是否有當前內核中沒有定義的symbol,如果有,在模塊搜索路徑中尋找其它模塊是否含有上面的symbol,有的話,自動加載關聯模塊
insmod對于這種情況,會報錯unresolved symbols

查看當前加載模塊:
lsmod
cat /proc/modules
  回復  更多評論 刪除評論  修改評論

# 如果你的模塊需要輸出符號給其他模塊使用 2010-11-15 18:12 小默

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);  回復  更多評論 刪除評論  修改評論

# 模塊參數 2010-11-15 18:40 小默

說10次hello,Mom
# insmod hellop howmany=10 whom="Mom"

--

static char *whom = "world"; //必須給默認值
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

--

S_IRUGO 允許所有人讀
S_IRUGO | S_IWUSR 允許所有人讀,允許root改變參數

如果參數被sysfs修改,不會通知模塊。不要使參數可寫,除非準備好檢測參數改變。

--
數組參數:
module_param_array(name, type, num, perm);   回復  更多評論 刪除評論  修改評論

# 設備主次編號 2010-11-16 13:24 小默

$ ls -l /dev
...
crw-rw---- 1 vcsa tty 7, 132 Nov 15 17:16 vcsa4
crw-rw---- 1 vcsa tty 7, 133 Nov 15 17:16 vcsa5
crw-rw---- 1 vcsa tty 7, 134 Nov 15 17:16 vcsa6
crw-rw---- 1 root root 10, 63 Nov 15 17:16 vga_arbiter
drwxr-xr-x 2 root root 80 Nov 15 17:16 vg_colorfulgreen
crw-rw-rw- 1 root root 1, 5 Nov 15 17:16 zero
...

輸出第一列是c的是字符設備,第一列b塊設備

修改日期前的兩個數字。
第一個是主設備編號:標識設備相連的驅動
第二個是次設備編號:決定引用哪個設備

-------
設備編號的內部表示

dev_t 在<linux/types.h>中定義,32位,12位主編號,20位次編號。

獲得一個dev_t的主或次編號:<linux/kdev_t.h>
MAJOR(dev_t dev);
MINOR(dev_t dev);

將主次編號轉換成dev_t:
MKDEV(int major, int minor);

--------
分配和釋放設備編號

建立一個字符驅動時,做的第一件事就是獲取一個或多個設備編號使用:
<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first要分配的起始設備編號
count請求的連續設備編號的總數
name連接到這個編號范圍的設備的名字,會出現在/proc/devices和sysfs中
成功返回0,出錯返回負的錯誤碼。

如果事先不知道使用哪個設備編號,使用下面函數,內核會分配一個主設備編號:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev是一個輸出參數,返回分配范圍的第一個數
firstminor請求第一個要用的次編號,常是0

設備編號的釋放:
void unregister_chrdev_region(dev_t first, unsigned int count);  回復  更多評論 刪除評論  修改評論

# scull安裝腳本 2010-11-16 13:57 小默

腳本scull_load:
1 #!/bin/sh
2 module="scull"
3 device="scull"
4 mode="664"

6 # invoke insmod with all arguments we got
7 # and use a pathname, as newer modutils don't look in. by default
8 /sbin/insmod ./$module.ko $* || exit 1 # 插入模塊,使用獲取的所有參數($*)

10 # remove stale nodes刪除無效的節點,不能刪除device0,device1,device2...阿。。TODO
11 rm -f /dev/${device}[0-3]
12 
13 major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #TODO 沒有搞明白
14 mknod /dev/${device}0 c $major 0 #創建4個虛擬設備
15 mknod /dev/${device}1 c $major 1
16 mknod /dev/${device}2 c $major 2
17 mknod /dev/${device}3 c $major 3
18 
19 # give appropriate group/permissions, and change the group.
20 # Not all distributions have staff, some have "wheel" instead. #TODO 神馬意思?
21 group="staff"
22 grep -q '^staff:' /etc/group || group="wheel"
23 # 改變設備的組和模式。腳本必須以root運行,但設備使用可能需要其它用戶寫。
24 chgrp $group /dev/${device}[0-3]
25 chmod $mode /dev/${device}[0-3]
26 
  回復  更多評論 刪除評論  修改評論

# file_operations結構 2010-11-16 15:24 小默

一些處理文件的回調函數

1 // init file_operations
2 struct file_operations scull_fops = {
3 .owner = THIS_MODULE,
4 .llseek = scull_llseek,
5 .read = scull_read,
6 .write = scull_write,
7 .ioctl = scull_ioctl,
8 .open = scull_open,
9 .release = scull_release,
10 };  回復  更多評論 刪除評論  修改評論

# 注冊字符設備 2010-11-16 15:25 小默

12 // 使用struct scull_dev結構表示每個設備
13 // TODO 沒有理解什么意思
14 struct scull_dev {
15 struct scull_qset *data; // pointer to first quantum set
16 int quantum; // the current quantum size
17 int qset; // the current array size
18 unsigned long sizee; //amount of data stored here
19 unsigned int access_key; // used by sculluid and scullpriv
20 struct semaphore sem; // matual exclusion semaphore
21 struct cdev cdev; // 字符設備結構
22 };
23 
24 // 初始化struct cdev,并添加到系統中
25 static void scull_setup_cdev(struct scull_dev *dev, int index)
26 {
27 int err, devno = MKDEV(scull_major, scull_minor + index);
28 
29 // TODO 初始化已經分配的結構. 不是很理解
30 // cdev結構嵌套在struct scull_dev中,必須調用cdev_init()來初始化cdev結構
31 cdev_init(&dev->cdev, &scull_fops);
32 dev->cdev.owner = THIS_MODULE;
33 dev->cdev.ops = &scull_fops;
34 // 添加到系統中
35 err = cdev_add(&dev->cdev, devno, 1);
36 if(err)
37 printk(KERN_NOTICE "Error %d adding scull%d", err, index);
38 }
  回復  更多評論 刪除評論  修改評論

# system-config-selinux 2010-11-27 02:54 小默

system-config-selinux
什么時候改了,汗 //TODO  回復  更多評論 刪除評論  修改評論

# read & write 2010-11-30 22:12 小默

// 表示每個設備
struct scull_dev{
struct scull_qset *data; // pointer to first quantum set
int quantum; // the current quantum size - 當前量子和量子集大小
int qset; // the current array size - 每個內存區域為一個量子,數組為一個量子集
unsigned long size; // amount of data stored here
unsigned int access_key; // used by sculluid and scullpriv
struct semaphore sem; // mutual exclusion semaphore

struct cdev cdev; // char device structure
};

// 量子集,即一個內存區域的數組
struct scull_qset{
void **data;
struct scull_qset *next;
};

ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_dev *dev = file->private_data;
struct scull_qset *dptr; // 量子集中的第一個元素
int quantum = dev->quantum, qset = dev->qset; // 當前量子和量子集大小
int itemsize = quantum * qset; // listitem中的字節數=量子大小*量子集大小
int item, s_pos, q_pos, rset;
ssize_t retval = 0;

if(down_interruptible(&dev->sem)) // TODO
return -ERESTARTSYS;
if(*f_pos > dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_pos;

// 查找listitem, qset index, and 量子中的偏移量
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;

// 遍歷list到右側
dptr = scull_follow(dev, item); // 量子集中的第一個元素
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;

// 只讀取到這個量子的尾部
if(count > quantum - q_pos)
count = quantum - q_pos;

if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;

out:
up(&dev->sem);
return retval;
}

// 一次處理單個量子
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; // value used in "goto out" statements
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;

// 查找列表元素,qset index and 量子中的偏移量
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;

// 遍歷list到右側
dptr = scull_follow(dev, item);
if(dptr == NULL):
goto out;
if(!dptr->data){
dptr->data = kmalloc(qset * sizeof(char), GPL_KERNEL);
if(!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(!dptr->data[s_pos])
goto out;
}

// 只寫到這個量子的結束
if(count > quantum-q_pos)
count = quantum - q_pos;
// 從用戶空間拷貝一整段數據to from count
if(copy_from_user(dptr->data[s_pos]+q_pos, buf, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;

// 更新size
if(dev->size < *f_pos)
dev->size = *f_pos;

out:
up(&dev->sem);
return retval;
}

//-------------------------------


// read和write的"矢量"版本
// readv輪流讀取指示的數量到每個緩存;writev收集每個緩存的內容到一起并且作為單個寫操作送出它們。
// count參數告訴有多少iovec結構,這些結構由應用程序創建,但是內核在調用驅動之前拷貝它們到內核空間。
ssize_t (*readv)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);

// iovec描述了一塊要傳送的數據
struct iovec{
void __user *iov_base; // 開始于iov_base(在用戶空間)
__kernel_size_t iov_len; // 并有iov_len長
};
  回復  更多評論 刪除評論  修改評論

# 重定向控制臺消息 2010-11-30 22:44 小默

// 重定向控制臺消息
// 使用一個參數指定接收消息的控制臺的編號
int main(int argc, char **argv)
{
char bytes[2] = {11, 0}; // 11 是 TIOCLINUX 的功能號

if(argc == 2) bytes[1] = atoi(argv[1]); // the chosen console
else{
fprintf(stderr, "%s: need a single arg\n", argv[0]);
exit(1);
}
// TIOCLINUX傳遞一個指向字節數組的指針作為參數,數組的第一個字節是一個數(需要指定的子命令)。
// 當子命令是11時,下一個字節指定虛擬控制臺。
if(ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0){ // use stdin
fprintf(stderr, "%s: ioctl(stdin, TIOCLINUX): %s\n", argv[0], stderror(errno));
exit(1);
}
exit(0);
}
  回復  更多評論 刪除評論  修改評論

# Implementing files in /proc 2010-12-04 00:42 小默

// 在proc里實現文件,在文件被讀時產生數據。
// 當一個進程讀你的/proc文件,內核分配了一頁內存,驅動可以寫入數據返回給用戶空間。
// buf 寫數據的緩沖區;start有關數據寫在頁中哪里;eof必須被驅動設置,表示寫數據結束;data用來傳遞私有數據。
// 假定不會有必要產生超過一頁的數據,并且因此忽略了start和offset值。
int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data)
{
int i, j, len = 0;
int limit = count - 80; // Don't print more than this

for(i = 0; i < scull_nr_devs && len <= limit; i++){ // TODO scull_nr_devs ?
struct scull_dev *d = &scull_devices[i];
struct scull_qset *qs = d->data;
if(down_interruptible(&d->sem))
return -ERESTARTSYS;
// 設備號,量子集大小,量子大小,存儲的數據量
len += sprintf(buf+len, "\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size); 
for(; qs && len <= limit; qs = qs->next){ //scan the list 遍歷量子鏈表
// 元素地址、鏈表地址
len += sprintf(buf + len, " item at %p, qset at %p\n", qs, qs->data); // %p 顯示一個指針
if(qs->data && !qs->next) // dump only the last item
for(j = 0; j < d->qset, j++){
if(qs->data[j])
len += sprintf(buf+len, "%4i: %8p\n", j, qs->data[j]);
}

}
up(&scull_devices[i]);
}
*eof = 1;
return len; // 返回實際在頁中寫了多少數據
}


/// 移除entry的一些問題
// 移除可能發生在文件正在被使用時。/proc入口沒有owner,沒有引用計數。
// 內核不檢查注冊的名字是否已經存在,可能會有多個entry使用相同名稱。而且在訪問和remove_proc_entry時,它們沒有區別。。。悲劇。。。  回復  更多評論 刪除評論  修改評論

# The seq_file interface 2010-12-04 10:56 小默

// 創建一個虛擬文件,遍歷一串數據,這些數據必須返回用戶空間。
// start, next, stop, show

// sfile 總被忽略;pos指從哪兒開始讀,具體意義完全依賴于實現。
// seq_file典型的實現是遍歷一感興趣的數據序列,pos就用來指示序列中的下一個元素。
// 在scull中,pos簡單地作為scull_array數組的索引。
// 原型
void *start(struct seq_file *sfile, loff_t *pos);
// 在scull中的實現
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if(*pos >= scull_nr_devs)
return NULL; // no more to read
return scull_devices + *pos; // 返回供迭代器使用的私有數據
}

// next把迭代器后挪一位,返回NULL表示沒有更多數據了
// v:上一次start/next調用返回的迭代器 TODO ???返回的不是私有數據么?
// pos: 文件中的當前位置。
void *next(struct seq_file *sfile, void *v, loff_t *pos);
// scull的實現
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if(*pos >= scull_nr_devs)
return NULL;
return scull_devices + *pos;
}

// 內核完成迭代器,調用stop清理
void stop(struct seq_file *sfile, void *v);
// scull沒有要清理的東西,stop方法是空的

// start到stop期間不會有sleep或者非原子操作,可以放心的在start中獲得信號量或自旋鎖。整個調用序列都是原子的。天書啊 TODO ???

// 在start和stop期間,內核調用show輸出迭代器v生成的數據到用戶空間
int show(struct seq_file *sfile, void *v);
// 輸出,等效于用戶空間的printf。返回非0值表示緩沖滿,輸出的數據會被丟棄。不過大多數實現都忽略返回值。
int seq_sprintf(struct seq_file *sfile, const char *fmt, ...);
// 等效于用戶空間的putc和puts
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);
// 如果s中有esc中的數據,這些數據用8進制輸出。常見的esc是"\t\n\\",用于保持空格,避免搞亂輸出。
int seq_escape(struct seq_file *m, const char *s, const char *esc);
// scull中show實現
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *dev = (struct scull_dev *)v;
struct scull_qset *d;
int i;

if(down_interrutible(&dev->sem))
return -ERESTARTSYS;
seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
(int)(dev - scull_devices), dev->qset,
dev->quantum, dev->size);
for(d = dev->data; d; d = d->next){ // 遍歷鏈表
seq_printf(s, " item at %p, qset at %p\n", d, d->data);
if(d->data && !d->next) // dump only the last item
for(i = 0; i < dev->qset; i++){
if(d->data[i])
seq_printf(s, " %4i: %8p\n", i, d->data[i]);
}
}
up(&dev->sem);
return 0;
}

// 迭代器:指向scull_dev的一個指針,囧。。。
// 迭代器操作集
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};

/// 用file_operations結構結構,實現內核read/seek文件的所有操作。
// 創建一個open方法,連接文件和seq_file操作 TODO 沒看懂
static int scull_proc_open(struct inode *inode, struct file *file)
{
// seq_open 連接文件和上面定義的scull_seq_ops
return seq_open(file, &scull_seq_ops);
}
// file_operations結構
static struct fle_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};

// 在/proc中創建設備
entry = create_proc_entry("scullseq", 0, NULL);
if(entry)
entry->proc_fops = &scull_proc_ops;
// create_proc_entry原型
struct proc_dir_entry *create_proc_entry(const char *name,
mode_t, mode,
struct proc_dir_entry *parent);  回復  更多評論 刪除評論  修改評論

# strace命令 - debug 2010-12-04 14:12 小默

略 O(∩_∩)O~~  回復  更多評論 刪除評論  修改評論

# ldd3_4.5_Debugging System Faults 2010-12-05 14:46 小默

講了兩部分

一、system opps
空指針引用,或者局部變量賦值覆蓋了原eip,導致頁錯誤

二、系統掛起 // TODO 看得云里霧里的
死循環等引起
假掛起:鼠標鍵盤等外設沒有響應了,系統實際正常。可以看時間。。。
插入schedule調用防止死循環,作用是允許其他進程從當前進程竊取時間。講了些弊端,沒看懂
sysrq:沒看懂
  回復  更多評論 刪除評論  修改評論

# re: 蓋樓 2010-12-26 06:13 小默

所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位。
原子操作主要用于實現資源計數,很多引用計數(refcnt)就是通過原子操作實現的。

原子類型定義:
typedef struct
{
volatile int counter;
}
atomic_t; 

volatile修飾字段告訴gcc不要對該類型的數據做優化處理,對它的訪問都是對內存的訪問,而不是對寄存器的訪問。  回復  更多評論刪除評論  修改評論

# spinlock_t 2010-12-26 16:24 小默

17 typedef struct {
18 volatile unsigned int lock;
19 #ifdef CONFIG_DEBUG_SPINLOCK
20 unsigned magic;
21 #endif
22 } spinlock_t;  回復  更多評論 <a id="AjaxHolder_Comments_CommentList_ctl24_DeleteLink"

ch03 字符驅動

3.4 字符設備注冊

struct cdev  <linux/cdev.h>

1. 分配并初始化一個cdev結構。

如果期望獲得一個獨立的cdev結構:

struct cdev *my_cdev = cdev_alloc();
my_cdev
->ops = &my_fops;


如果想把cdev嵌入到自定義的設備結構中:

void cdev_init(struct cdev *cdev, struct file_operations *fops);


上面兩種方法,都需要初始化owner成員為THIS_MODULE

2.把新建立的cdev結構告訴內核

// 把已經分配、初始化的cdev結構通知內核。立即生效。出錯時返回一個負的錯誤碼。
int cdev_add(struct cdev *dev, 
               dev_t num,           
// 設備響應的第一個設備號
               unsigned int count);  // 關聯到設備上的設備號數量


3.移除cdev結構

// 從系統中移除一個字符設備
void cdev_del(struct cdev *dev);


scull中字符設備注冊

 // 注冊字符設備
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    
int err, devno = MKDEV(scull_major, scull_minor + index); // MKDEV 把主次設備號合成為一個dev_t結構

    
// 分配并初始化cdev結構。struct cdev內嵌在struct scull_dev中
    cdev_init(&dev->cdev, &scull_fops);
    dev
->cdev.owner = THIS_MODULE;
    dev
->cdev.ops = &scull_fops;
    
// 通知內核,立即生效
    err = cdev_add (&dev->cdev,     // struct cdev *p: the cdev structure for the device
                    devno,          // dev_t dev: 第一個設備號
                    1);             // unsigned count: 該設備連續次設備號的數目
    /* Fail gracefully if need be */
    
if (err)
        printk(KERN_NOTICE 
"Error %d adding scull%d", err, index);
}




// 注冊字符設備過去的方法
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
int unregister_chrdev(unsigned int major, const char *name);
 

3.5 open 和 release

3.5.1 open 方法

open方法在驅動中用來做一些初始化準備工作。

在多數驅動中,open進行的工作:
- 檢查設備特定的錯誤(device-not-ready等)
- 如果設備第一次打開,初始化設備
- 更新 f_op 指針,如果需要的話
- 給 filp->private_data 賦值

open原型:

int (*open)(struct inode *inode, struct file *filp);

如何確定打開的是哪個設備:
- inode中i_cdev成員,是之前建立的cdev結構。通常我們需要的不是cdev,而是包含cdev的scull_dev結構,這時使用    
container_of(pointer, container_type, container_field);
- 查看存儲在inode中的次設備號。如果使用register_chrdev注冊設備,必須采用這種方法。

scull設備是全局和永久的,沒有維護打開計數,不需要"如果設備第一次打開,初始化設備"的操作。  // TODO
scull_open:
/// 打開設備
int scull_open(struct inode *inode, struct file *filp)
{
    
struct scull_dev *dev; /* device information */

    
// 文件私有數據,設置成對應的scull_dev
    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp
->private_data = dev; /* for other methods */

    
/* now trim to 0 the length of the device if open was write-only */
    
// 文件以只讀模式打開時,截斷為0
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        
if (down_interruptible(&dev->sem)) // 申請新號量,可中斷
            return -ERESTARTSYS;
        
// 釋放整個數據區
        scull_trim(dev); /* ignore errors */ 
        up(
&dev->sem);  // 釋放信號量
    }
    
return 0;          /* success */
}

3.5.2 release方法

release進行的工作:
- 釋放open在file->private_data中申請的一切
- 在調用最后一個close時關閉設備

scull的基本形式沒有需要關閉的設備。
scull_release:
int scull_release(struct inode *inode, struct file *filp)
{
    
return 0;
}


close 和 release
并不是每一個close系統調用,都會引起對release的調用。
內核對一個打開的file結構,維護一個引用計數。dup和fork都只遞增現有file結構的引用計數。只有當引用計數為0時,對close的調用才引起對release的調用。
3.6 scull的內存使用 

how & why 執行內存分配
scull 使用的內存區(被稱為device)長度可變,隨寫的內容增多而增長;修剪通過用短文件重寫device實現。

1. 內核中管理內存使用的兩個核心函數:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
kmalloc試圖申請size字節內存,返回指向內存的指針,或者申請失敗時返回NULL。flags指定如何使用內存,在這里先用GFP_KERNEL。
kfree用來釋放kmalloc申請的內存。不能用kfree釋放其它不是kmalloc申請的內存。

kmalloc不是申請大塊內存的有效方法,但為了代碼簡單易懂,在這里暫時使用kmalloc。另外,我們沒有限制“device”區域大小:理論上不該給數據施加武斷的限制;實踐上scull可以暫時吃光所有系統內存來模擬低內存環境下的測試。可以使用 cp /dev/zero /dev/scull0 吃光所有RAM內存,也可以用 dd 工具指定分配給scull的內存。

2. scull device結構:
在scull中,每個device是一系列指向scull_device結構的指針組成的鏈表。每個scull_device結構默認指向最多4M的內存區:有多個量子集,每個量子集有多個量子。每個scull_device的布局如下:


struct scull_qset {
    void **data;
    struct scull_qset *next;
};
3. 量子和量子集大小設置:
在scull中單寫一個字節需要8000kb或1200kb內存:量子用掉4000;量子集用掉4000kb或8000kb(根據在目標平臺上一個指針代碼32位或者64位)。但是,當寫入數據量比較大時,鏈表的開銷還可以:每4M字節一個鏈表。

為量子和量子集選擇合適的值是策略問題,而不是機制。scull驅動不應該強制使用特定值,而是可以由用戶設置:編譯時改變scull.h中宏SCULL_QUANTUM和SCULL_QSET;模塊加載時設置scull_quantum和scull_qset;運行時通過ioctl改變。

4. scull中的內存管理的代碼段

// 釋放整個數據區。簡單遍歷列表并且釋放它發現的任何量子和量子集。
// 在scull_open 在文件為寫而打開時調用。
// 調用這個函數時必須持有信號量。
/*
 * Empty out the scull device; must be called with the device
 * semaphore held.
 */
int scull_trim(struct scull_dev *dev)
{
    struct scull_qset *next, *dptr;
    int qset = dev->qset;   /* "dev" is not-null */ // 量子集大小
    int i;

    // 遍歷多個量子集。dev->data 指向第一個量子集。
    for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
        if (dptr->data) { // 量子集中有數據
            for (i = 0; i < qset; i++) // 遍歷釋放當前量子集中的每個量子。量子集大小為qset。 
                kfree(dptr->data[i]);
            kfree(dptr->data); // 釋放量子指針數組
            dptr->data = NULL; 
        }
        // next獲取下一個量子集,釋放當前量子集。
        next = dptr->next;
        kfree(dptr);
    }
    // 清理struct scull_dev dev中變量的值
    dev->size = 0;
    dev->quantum = scull_quantum;
    dev->qset = scull_qset;
    dev->data = NULL;
    return 0;
}

3.7 read 和 wirte

read和write進行在內核和用戶空間之間復制數據的工作。
原型:
ssize_t read(struct file *filp, char _ _user *buff,
    size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char _ _user *buff,
    size_t count, loff_t *offp);
filp是指向文件的指針。count是要讀寫字節數。buff指向用戶空間地址。offp是指向long offset type對象的指針,指明要訪問的文件位置。
返回實際傳輸的字節數,出錯返回負值。

訪問用戶空間地址char __user *buff:

驅動中不應該直接對用戶空間地址指針 *buff 解引用。原因:
- 用戶空間地址指針在內核中可能根本無效,這取決于主機體系結構和內核配置。
- 即便這個指針在內核中代表同一個東西,但用戶空間是分頁的,地址可能不在RAM中。直接解引用 用戶空間地址指針 可能引發頁錯誤。
- 用戶程序提供的指針可能是錯誤的或者惡意的。盲目解引用用戶提供的指針,將允許用戶訪問系統內存中任何地方。

應該通過內核提供的函數訪問用戶空間。
scull中復制一整段數據,使用下面兩個函數:
unsigned long copy_to_user(void _ _user *to,
                           const void *from, 
                           unsigned long count);
unsigned long copy_from_user(void *to, 
                             const void _ _user *from, 
                             unsigned long count);
使用上面兩個函數需要注意的一點:當用戶空間地址不在內存中時,進程會投入睡眠等待內存調度,所以任何存取用戶空間的函數必須是可重入的。
數據傳送完成后需要修改*offp為當前的文件位置。
read參數:

讀device時需要考慮的特殊情況:當前讀位置大于設備大小

這種情況發生在:進程a在讀設備,同時進程b寫設備將長度截斷為0。此時scull的read返回0表示已到文件尾(沒有更多的數據可以讀)

ch04 調試技術

4.1 內核中的調試支持

編譯內核時,"kernel hacking"菜單中有一些調試選項。
書中列了一些,暫時沒有看
4.2 debugging by print

掃下目錄,暫時沒有興趣細看o(╯□╰)o

4.2.1 printk
4.2.2 重定向控制臺消息
4.2.3 消息是如何被記錄的
4.2.4 關閉或打開消息
4.2.5 速率限制
4.2.6 打印設備號 
4.3-4.6 心情不好,不想看鳥~

ch05 并發和競爭

早期內核不支持多處理器,只有硬件中斷服務涉及到并發問題。
5.1 scull 的缺陷
write邏輯中的一段代碼,scull需要考慮申請的內存是否獲得:
    if (!dptr->data[s_pos]) {
        dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
        if (!dptr->data[s_pos])
            goto out;
    }
假設在同一時刻,A和B兩個進程試圖獨立地寫同一個scull設備的相同偏移。兩個進程同時到達if判斷,此時sptr->data[s_pos]為NULL,兩個進程都申請了一塊內存賦值給dptr->data[s_pos]。dptr->data[s_pos]最后只會保留一個值,假設B賦值在A之后,A申請的內存就被丟掉無法再獲得,從來產生內存泄露(memory leak)。
上面是一個競爭狀態(race condition)的示例,產生競爭狀態的原因是沒有控制對 共享數據 的訪問。
5.2 并發和它的管理 

競爭條件是伴隨著資源的共享訪問而來的。內核代碼應該有最小的共享,其中最明顯的應用就是盡可能少地使用全局變量。

共享無處不在。硬件是共享的,軟件資源常常需要共享給多個線程。需要牢記全局變量遠非唯一的共享方式。當把指針傳遞到內核其他部分時,可能就創建了一個新的共享。

資源共享的hard rule:如果硬軟件資源被多于一個執行線程共享,并且不同線程對統一資源可能產生不同的視圖,則必須明確管理對資源的訪問。比如5.1末尾提到的例子,B看不到A已經為dptr->data[s_pos]分配了內存,于是B才重新分配了一塊內存,并覆蓋掉了A的內存指針。

另外一個重要的規則:如果內核代碼創建了一個可能被內核其它部分共享的對象,這個對象必須一直存在直到沒有外部引用。這里有2層意思:只有做好所有準備后才能告訴內核設備存在了;必須記錄共享對象的引用數。
5.3 信號量和互斥

我們的目的是對scull數據結構的操作是原子的(atomic):也就是說在有其它線程會影響到這個數據結構前,操作必須一次完成。為此需要設置一個臨界區(critical sections):其中的代碼在任何時刻只能由一個線程執行。

內核提供了多種原語來滿足不同臨界區的要求。在這個例子中,scull驅動可以睡眠等待:只有用戶的直接請求會產生對scull數據的訪問,不會有來自中斷處理或者異步上下文中的訪問,另外scull也沒有持有關鍵的系統資源。所以可以使用鎖機制:可能導致進程在等待訪問臨界區時睡眠。

另外很重要的一點,我們在臨界區中將執行另外一個可能會導致線程休眠的操作:kmalloc申請內存。所以我們選用的鎖原語,必須能夠在擁有鎖的線程休眠時正常工作。在這里最合適的機制是信號量。

一個信號量本質上是一個整數值,它和一對PV函數混合使用。希望進入臨界區的線程在相關信號量上調用P函數:如果信號量值>0,值減1,,線程繼續;否則線程休眠等待其它人釋放信號量。用V解鎖信號量:增加信號量值,并在必要時喚醒等待線程。

信號量的初始值表示總共有幾個資源可供同時使用。當信號量用于互斥時(mutual exclusion,避免多個線程同時在一個臨界區中運行),信號量的值應該初始化為1。這時,信號量也常常稱為互斥體。

訪問臨界區的原語
| 鎖:可能導致進程休眠。
| | 信號量:能夠在擁有鎖的線程休眠時正常工作。
| | | 互斥體:初始化為1的信號量,用戶互斥。

5.3.1 Linux 信號量的實現

<linux/semaphore.h>
struct semaphore

信號量有多種聲明和初始化的方法。

void sema_init(struct semaphore *sem, int val);
創建信號量sem,賦予初始值val。

DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
第一個宏初始化信號量name為1。
第二個宏初始化信號量name為0。這時互斥體的初始狀態是鎖定的,在允許任何線程訪問之前,必須被顯示解鎖。

void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
用于互斥體必須在運行時初始化,例如在動態分配互斥體的情況下。

void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
down是linux下的P操作,減少信號量的值,并在必要時一直等待。
down_interruptible在等待時可被用戶中斷。如果操作被中斷,該函數返回非0值,并且線程不會獲得信號量。在使用down_interrupible時,必須檢查返回值。非中斷操作會建立不可殺進程(ps輸出中的D state),一般不提倡使用。
down_trylock如果不能獲得信號量,立即返回非零值,永遠不會休眠。

void up(struct semaphore *sem);
up是linux下的V操作。
任何獲得信號量的線程都必須通過up釋放信號量。如果線程在擁有信號量的時候出錯,必須在將錯誤返回給調用者之前,釋放信號量。

5.3.2 在scull中使用信號量

使用鎖原語的關鍵:明確要保護的資源;確保對該資源的每次訪問都使用了合適的鎖原語。

在scull驅動中,要限制訪問的資源都在scull_dev結構中。我們為每個scull設備使用一個單獨的信號量:沒有理由讓一個進程在其他進程訪問別的設備時等待;允許在不同設備上的操作并行進行。

信號量必須在scull設備對系統中其他部分可用前被初始化。我們在scull_init_module中,在調用scull_setup_cdev(向內核注冊設備)前調用了init_MUTEX。
必須仔細檢查代碼,確保在不擁有該信號量時不訪問scull_dev結構。例如,在scull_write開始處先申請dev->sem。
最后,scull_write必須釋放信號量。

5.3.3 Reader/Writer 信號量

semaphore對所有要訪問數據的線程互斥,但實際上多個線程同時讀數據不會出錯。

rwsem允許一個線程寫數據 或者 多個線程讀數據,寫優先級大于讀。也就是說,讀寫不能同時進行,讀寫同時申請rwsem時,寫優先獲得。結果是,如果有大量線程等待寫數據,可能讀申請很久都得不到滿足,餓死。所以,最好在很少需要寫數據,并且寫數據用時很短時使用rwsem。

下面是使用rwsem的接口:

<linux/rwsem.h>
struct rw_semaphore

void init_rwsem(struct rw_semaphore *sem);
rwsem必須在運行時顯式的初始化。

void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);
down_read可能會使進程進入不可中斷的休眠。
down_read_trylock在獲得訪問權限時返回非0,其它情況下返回0。 --注意返回值和一般內核函數不同:一般內核函數出錯返回非0。

void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
在結束修改后調用downgrage_write,允許其它讀用戶訪問。
5.4 complete 機制

問題提出:

內核編程中經常遇到一種情況,啟動當前線程外部進行一項任務,并等待它完成。例如:創建一個新的內核線程或用戶空間進程;對一個已有進程的某個請求;某個硬件動作。

這時可以使用信號量來同步這兩個任務:start外部任務后馬上down(sem);當外部任務完成工作時up(sem)。
    struct semphore sem;

    init_MUTEX_LOCKED(&sem);
    start_external_task(&sem);
    down(&sem);

    當外部任務完成工作時,up(&sem);

但信號量并不是完成這項工作的最好方法:這里調用down的線程幾乎總是要等待,會影響性能;信號量聲明為自動變量,在某些情況下,信號量圍在外部線程調用up前消失。

解決方法:

complete機制,允許一個線程告訴另一個線程某項工作已完成。

<linux/completion.h>
struct completion

DECLARE_COMPLETION(my_completion);
創建completion。

struct completion my_completion;
/* .. */
init_completion(&my_completion);
動態的創建和初始化completion。

void wait_for_completion(struct completion *c);
等待completion。該函數執行的是非中斷等待,如果沒有人完成該任務,則將產生一個不可殺進程。

void complete(struct completion *c);
void complete_all(struct completion *c);
complete喚醒一個線程,complete_all喚醒等待的所有線程。
用complete完成的completion可以重復使用,用complete_all完成的completion在重新使用前要重新初始化:INIT_COMPLETION(struct completion c)。

completion機制的典型應用:內核模塊exit時等待線程終止
thread
{
    ...
    while(1){
    if(condition) break;
    }
    收尾工作
    complete_and_exit(struct completion *c, long retval);
}

module_exit
{
    滿足condition
    wait_for_complete
}

TODO:wait_for_complete和down在等在時有什么區別?為什么后者效率就高了?
5.5 自旋鎖

自旋鎖可在不能休眠的代碼中使用,如中斷處理例程。 // TODO:休眠和自旋 有什么區別?優劣之處?

一個自旋鎖是一個互斥設備,只能有兩個值:locked和unlocked。它通常是一個int值的單個bit。申請鎖的代碼test這個bit:如果鎖可用,則set這個bit,執行臨界區代碼;否則代碼進入重復測試這個bit的忙循環,直到鎖可用。

自旋鎖的實現:"test and set"的過程必須以原子方式完成;當存在自旋鎖時,申請自旋鎖的處理器進入忙循環,做不了任何有用的工作。

自旋鎖最初是為了在多處理器上使用設計的。工作在可搶占內核的單處理器行為類似于SMP。如果非搶占式的單處理器系統進入自旋,會永遠自旋下去;因此,非搶占式的單處理器上的自旋鎖被優化為什么都不過(但改變IRQ掩碼狀態的例程是個例外)。

5.5.1 自旋鎖API介紹

<linux/spinlock.h> spinlock_t

初始化,在編譯時或運行時:
spinlock_t mylock = SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);

獲得鎖:
void spin_lock(spinlock_t *lock);

釋放已經獲得的鎖:
void spin_unlock(spinlock_t *lock);

5.5.2 自旋鎖和原子上下文

使用自旋鎖的核心規則:任何擁有自旋鎖的代碼都必須是原子的。它不能休眠,不能因為任何原因放棄處理器(除了某些中斷服務)。

許多內核函數可以休眠:在用戶空間和內核空間復制數據,用戶空間頁可能要從磁盤上換入,需要休眠;分配內存的操作(kmalloc)在等待可用內存時會休眠。
內核搶占的情況由自旋鎖代碼本身處理:代碼擁有自旋鎖時,在相關處理器上的搶占會被禁止。

在擁有自旋鎖時,需要禁止中斷。考慮一個例子:驅動程序已經獲得了一個控制對設備訪問的自旋鎖;設備產生了一個中斷,中斷處理例程被調用,中斷處理例程在訪問這個設備之前,也需要這個鎖;如果中斷處理例程在最初擁有鎖的代碼所在的處理器上自旋,非中斷代碼將沒有任何機會來釋放這個鎖,處理器將永遠自旋下去。 //TODO Q:在一個CPU上被中斷的代碼,必須在同一CPU上恢復執行?

擁有鎖的時間要盡可能短:避免其它CPU自旋等待相同鎖的時間過長;本CPU無法調度高優先級進程(搶占被禁止)。

5.5.3 自旋鎖函數

獲得自旋鎖的函數有4個:
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock);
spin_lock_irpsave會在獲取自旋鎖之前,禁止本地CPU上的中斷,并把先前中斷狀態保存在flags中。spin_lock_irp不跟蹤中斷標志,用于明確知道沒有其它代碼禁止本地處理器的中斷時。spin_lock_bh只禁止軟件中斷,會讓硬件中斷繼續打開。
如果某個自旋鎖可以被運行在軟件中斷或硬件中斷上下文的代碼中獲得,則必須使用某個禁止中斷的spin_lock形式。

對應有4個釋放自旋鎖的函數:
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
spin_lock_irqsave和spin_unlock_irqsave必須在同一函數中調用。

非阻塞的自旋鎖操作:
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
spin_trylock和spin_trylock_bh在獲得自旋鎖時返回非零值,否則返回0.

5.5.4 讀取者/寫入者 自旋鎖

與rwsem類似,允許多個reader同時進入臨界區,writer必須互斥訪問。
可能造成reader饑餓,原因同rwsem。

下面是相關API。

靜/動態初始化:

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */

rwlock_t my_rwlock;
rwlock_init(&my_rwlock);  /* Dynamic way */

reader API:

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);

void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

writer API:

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);

void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
5.6 鎖陷阱

5.6.1 不明確的規則

在創建可被并行訪問對象的同時,定義控制訪問的鎖。

如果某個已經獲得鎖的函數,試圖調用其他同樣試圖獲得同一鎖的函數,代碼就會死鎖。

有些函數需要假定調用者已經獲得了某些鎖:如果函數供外部調用,必須顯式的處理鎖定;如果是內部函數,必須顯式說明這種假定,防止以后忘記。

5.6.2 鎖的順序規則

在必須獲得多個鎖時,應該始終以相同的順序獲得:假設線程1和線程2都要獲得LOCK1、LOCK2,如果線程1拿到了LOCK1,同時線程2拿到了LOCK2,就出現死鎖了。

兩條TIPS。如果需要獲得一個局部鎖(如設備鎖)和一個內核更中心位置的鎖,先獲得局部鎖。如果需要同時擁有信號量和自旋鎖,必須先獲得信號量:在擁有自旋鎖時調用down(可能導致休眠)是個嚴重錯誤。

5.6.3 細粒度鎖和粗粒度鎖的對比

粗粒度鎖影響性能,細粒度鎖增加復雜性。

應該在最初使用粗粒度鎖,抑制自己過早考慮優化的欲望,因為真正的性能瓶頸常出現在非預期的地方。
ch06 高級字符驅動程序操作

6.1 ioctl

支持通過設備驅動程序執行各種類型的硬件控制。

用戶空間原型:
int ioctl(int fd, unsigned long cmd, ...);

內核空間原型:
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
注意不管用戶控件傳遞的參數的值還是指針,在內核中都用unsigned long表示。

大多數ioctl實現都包括一個switch語句,根據cmd參數選擇對應操作。

6.1.1 選擇ioctl命令

兩個關于選擇ioctl編號的文檔。include/asm/ioctl.h定義了要使用的位字段:類型(幻數)、序數、傳送方向、參數大小等。Documentation/ioctl-numbet.txt中羅列了內核所使用的幻數,選擇自己的幻數時注意避免和內核沖突。

4個位字段:type是8位寬的幻數;number是8位寬的序數;direction數據傳輸方向:該字段是個位掩碼,可用AND從中分解出_IOC_READ和_IOC_WRITE;size是所涉及的用戶數據大小。

構造cmd的宏(在<linux/ioctl.h>中包含的<asm/ioctl.h>中定義):
_IO(type, nr)構造無參數的ioctl命令;_IOR(type, nr, datatype)讀數據;_IOW(type, nr, datatype)寫數據;_IORW(type, nr, datatype)雙向傳輸。
type和number通過參數type和nr傳入,size通過對datatype取sizeof獲得。

對cmd解碼的宏:
_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr).

6.1.2 返回值

cmd不匹配任何合法操作時,返回-EINVAL。

6.1.3 預定義命令

內核預定義了一些ioctl命令。其中一類是可用于任何文件的命令,幻數都是“T”。

6.1.4 使用ioctl參數

如果ioctl的附加參數是指向用戶空間的指針,必須確保指向的用戶空間是合法的。

acess_ok函數驗證地址:檢查所引用的內存是否位于當前進程有訪問權限的區域;特別地,要確保地址沒有指向內核空間的內存區。

6.1.5 權能與受限操作

<linux/capability.h>

基于權能(capability)的系統把特權操作分成獨立的組。

<sys/sched.h>
int capable(int capability);
檢查進程是否擁有權能capability。
6.3 poll 和 select

驅動程序決定,是否可以對一個或多個打開的文件,做非阻塞的讀取或寫入。

驅動程序的poll方法:
unsigned int (*poll)(struct file *filp, poll_table *wait);
當用戶程序在與驅動相關聯的文件描述符上執行poll/select時被調用。
分兩步處理:調用poll_wait向poll_table添加一個或多個 可指示poll狀態變化的等待隊列;返回一個描述操作是否可以利益執行的位掩碼。

void poll_wait(struct file *, wait_queue_head_t *, poll_table);
ch12 pci驅動

PCI是一套完整的規則,定義計算機的各個部分如何交互。PCI驅動負責發現硬件并訪問。

PCI體系結構的目的:提高計算機和外設間數據傳輸性能;盡可能地和平臺無關;簡化外設的添加和移除。

PCI設備在啟動時配置。設備驅動必須可以訪問配置信息,并完成設備初始化。不需要PCIqudo
12.1.1 PCI尋址

struct dev_pci 代表pci設備。

每個PCI外設由 <總線號,設備號,功能號> 確定。Linux另外引入了PCI domains的概念,每個domain可以有256根總線,每根總線可以有32個設備,每個設備下可以有8個功能號(例如一個audio設備有一個CD-ROM功能號):所以每個功能可以由16位的地址表示。
查看16位硬件地址:lspci;/proc/pci;/proc/bus/pci。地址一般按16進制表示:<8位總線號:8位設備號和功能號>;<8位總線號:5位設備號,3位功能號>;<domain:bus:device:funciton>。

較新的工作站至少有兩根總線。每兩根總線用特殊外設bridges連接。PCI系統的總體布局是一棵樹:總線通過bridges連接到上層總線上,直到樹根(0號總線)。

外設板答復3類地址空間的查詢:內存單元,I/O端口,配置寄存器空間。同一PCI總線上的所有設備共享前兩個地址(也就是說,訪問一個內存單元時,該總線上的所有設備都能看到總線周期)。配置空間利用了地理尋址,同一時刻只對應一個slot,不會產生沖突。

下面的一堆沒有看懂。

12.1.2 啟動時間 

當PCI設備上電時,它只響應配置信息的存取:設備沒有內存空間,也沒有I/O端口被映射到內存空間;中斷報告也被關閉。

系統啟動時,固件(bios或內核)讀取每個PCI外設的配置信息,為其分配地址去。在驅動存取設備時,它的內存區和I/O區已經被映射到處理器的地址空間。

通過讀/proc/bus/pcidrivers 和 /proc/bus/pci/*/* 可查看PCI設備列表和配置寄存器。

12.1.3 配置寄存器和初始化

所有PCI設備都有至少256字節的配置地址空間:前64個字節是標準的,剩下的依賴于設備。

PCI寄存器是小端模式。如果需要在主機序和PCI序之間轉換,參考<asm/byteorder.h>

寄存器類型:
vendorID    16位,硬件制造商
deviceID    16位,由硬件制造商選擇。vendorID和deviceID合起來被稱作“簽名”,驅動依靠簽名來標識設備。
classID 16位,類。一個驅動可以同時支持幾個屬于同一類,但有不同簽名的設備。
subsystem venderID/subsystem deviceID 進一步標識設備。

struct pci_device_id 定義一個驅動支持的PCI設備的列表:
__u32 vendor;    // 驅動支持的設備的PCI供應商和設備ID,PCI_ANY_ID表示支持任何。
__u32 device;
__u32 subvendor;
__u32 subdevice;
__u32 class;    // 驅動支持的PCI設備種類,PCI_ANY_ID表示支持任何類
__u32 class_mask;
kernel_ulong_t driver_data;    // 用來持有信息,也可以來標識設備。

初始化一個struct pci_device_id:
PCI_DEVICE(vendor, device);
PCI_DEVICE(device_class, device_class_mask);

12.1.4 MODULEDEVICETABLE宏

MODULEDEVICETABLE將pci_device_id結構輸出到用戶空間,供熱插拔和模塊加載系統知道驅動模塊匹配的硬件。

該宏創建 指向struct pci_device_id的 局部變量_mod_pci_device_table;depmod遍歷所有模塊,將查找到的_mod_pci_device_table添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap中;當有新的PCI設備連到計算機上,熱插拔系統使用文件modules.pcimap找到對應的驅動并加載。

12.1.5 注冊一個PCI驅動

PCI驅動結果,包含許多回調函數和變量。
struct pci_driver 
const char *name;    // 驅動名稱,在內核所有PCI驅動里必須是唯一的。可以在/sys/bud/pci/drivers中查看。
const struct pci_device_id *id_tables;    // 指向struct pci_device_id表的指針。
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);    // PCI核心發現與該驅動匹配的struct pci_dev時,調用probe。如果驅動支持該pci_dev,正確初始化設備并返回0;否則返回一個錯誤值。
int (*remove)(struct pci_dev);    // 當struct pci_dev從系統中移除時,由PCI核心調用;PCI驅動被移除時,也會調用remove函數來移除驅動支持的設備。
int (*suspend)(struct pci_dev *dev, u32 state);    // 當struct pci_dev被掛起時,由PCI核心調用。state指明掛起狀態。可選的實現。
int (*resume)(struct pci_dev *dev);    // 當pci_dev恢復時,由PCI核心調用。

總之,創建一個pci驅動時,至少只需初始化4個成員:
static struct pci_driver pci_driver = {
    .name = "pci_skel",
    .id_table = ids,
    .probe = probe,
    .remove = remove,
};

注冊一個PCI驅動:
int pci_register_driver(struct pci_driver *drv);
.注冊struct pci_driver到PCI核心,成功返回0,失敗返回一個錯誤值。它不返回綁定到驅動上的設備號;實際上,當沒有設備被綁定到驅動上時,也算注冊成功:驅動把自己注冊到PCI核心,當PCI核心發現有新設備時,PCI核心調用驅動提供的回調函數初始化設備。

卸載一個PCI驅動:
void pci_unregister_driver(struct pci_driver *drv);
該函數移除綁定到這個驅動上的所有設備,然后調用驅動的remove函數。 TODO Q:調用remove 和 移除綁定到驅動上的設備 不一樣么?

12.1.6 老式PCI探測

老版本內核中,PCI驅動手動瀏覽系統中PCI設備。在2.6中已經去掉了驅動瀏覽PCI設備列表的功能(處于安全考慮),而是由PCI核心發現設備時調用已經注冊的驅動。

查找一個特定PCI設備:
struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from);
struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from);
如果找到匹配設備,遞增struct pci_dev中的found引用計數;當驅動用完該pci_dev時,需要調用pci_dev_put遞減found引用計數。
from表示從哪個設備開始探索,如果from設為NULL,表示返回第一個匹配的設備。
該函數不能從中斷上下文中調用。

在指定的pci bus上查找設備:
struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn);
devfn指明pci設備的功能號。

12.1.7 使能pci設備

int pci_enable_device(struct pci_dev *dev);
在PCI驅動的probe()函數中,必須調用pci_enable_device(),之后驅動才可以存取PCI設備的資源。

12.1.8 存取配置空間

驅動已經探測到設備后,需要讀寫3個地址空間:內存、I/O端口和配置空間。其中,需要先讀取配置空間,獲取設備被映射到的內存和I/O空間。

配置空間可以通過8/16/32為數據傳輸來讀寫:
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
where指從配置空間開始的字節偏移,讀取到的值從val返回。
word和dword函數負責從小端到處理器本地字節序的轉換。

12.1.9 存取I/O和內存空間

一個PCI設備至多有6個I/O地址區:每個地址區要么是內存區,要么是I/O區。I/O寄存器不應該被緩存,所以對于在內存中實現I/O寄存器的設備,必須在配置寄存器中設置一個"內存可預取"位,表明內存區是否可預取。

一個PCI設備使用6個32位的配置寄存器,指明它的地址區大小和位置。這6個配置寄存器的符號名是PCI_ADDRESS_0到PCI_BASE_ADDRESS_5。

獲取設備的地址區:
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
返回第bar個地址區的第一個地址,最后一個地址,flags資源標識。
資源標識定義在<linux/ioport.h>中。

12.1.10 PCI中斷

linux啟動時,固件給設備分配一個唯一的中斷號。
PCI設備的中斷號存儲在配置寄存器60(PCI_INTERRUPT_LINE),1字節寬。
如果不支持中斷,寄存器61(PCI_INTERRUPT_PIN)是0.
ch17 網絡驅動程序

網絡驅動程序異步接收來自外部世界的數據包,發送給內核。

octet指一組8個的數據位,是能為網絡設備和協議所理解的最小單位。


17.1 snull設計

snull模擬了和遠程主機的回話,不依賴于硬件。只支持ip流。

17.1.1 分配ip號

snull創建了兩個接口。通過一個接口傳輸的數據,都會出現在另外一個接口上。

如果給兩個接口使用相同的ip號,內核會使用回環通道,而不會通過snull,所以在傳輸中需要修改源及目的地址。 // TODO


17.2 連接到內核

推薦閱讀 lookup.c plip.c e100.c

17.2.1 設備注冊

網絡接口沒有主次設備號一類的東西,而是對每個新檢測到的接口,向全局的網絡連接設備鏈表中插入一個數據結構 net_device。
<linux/netdevice.h>
struct net_device *snull_devs[2];
struct net_device包含了一個kobject和引用計數。

由于和其它結構相關,必須動態分配:
struct net_device *alloc_netdev(int sizeof_priv, const char *name, void(*setup)(struct net_device *));
sizeof_priv是驅動程序私有數據大小;name接口名稱,在用戶空間可見;setup初始化函數,用來設置net_device結構剩余部分。
網絡子系統對alloc_netdev函數,為不同種類的接口封裝了許多函數,最常用的是alloc_etherdev<linux/etherdevice.h>。

調用register_netdev注冊設備,立即生效。

17.2.2 初始化每個設備

在調用register_netdev之前,必須完成初始化。
先調用ether_setup(struct net_device)為結構設置默認值,然后再給我們關心的成員賦值。在scull中,flags設置為IFF_NOARP,表示接口不使用ARP協議;hard_header_cache設置為NULL,禁止對ARP的緩存。

struct net_device中priv成員保存設備私有數據,和net_device結構同時分配內存(字符驅動中fops->private是單獨分配的)。不推薦直接訪問priv指針,一般通過netdev_priv函數訪問:
struct scull_priv *priv = netdev_priv(dev);

17.2.3 模塊的卸載

for (i = 0; i < 2;  i++) {
    if (snull_devs[i]) {
        unregister_netdev(snull_devs[i]);
        snull_teardown_pool(snull_devs[i]);
        free_netdev(snull_devs[i]);
    }
}

unregister_netdev從系統中刪除接口;free_netdev將net_device結構返回給系統(引用計數減1);snull_teardown_pool是內部清楚函數,必須在free_netdev之前調用:調用free_netdev之后不能再引用設備或私有數據區。
17.3 net_device結構細節

//一堆成員列表,米有看。。。
17.4 打開和關閉

在接口能夠傳輸數據包之前,內核必須打開接口并賦予其地址。
用ifconfig給接口賦予地址時,執行兩個任務:通過ioctl(SIOCSIFADDR)賦予地址,該任務由內核進行;通過ioctl(SIOCSIFFLAGS)設置dev->flags中的IFF_UP標志以打開接口,該命令會調用設備的open方法。

在接口被關閉時,ifconfig使用ioctl(SIOCSIFFLAGS)清除IFF_UP標志,然后調用stop函數。

open的工作:將MAC地址從硬件設備復制到dev->dev_addr;啟動接口的傳輸隊列(允許接口傳輸數據包)。

stop的工作:關閉接口的傳輸隊列、

void netif_start_queue(struct net_device *dev);
void netif_stop_queue(struct net_device *dev);
cg_cdev

insmod后
# cat /dev/devices
可以看到字符設備中列的有cg_cdev。
--
    /dev/...目錄和/proc/devices/...下的設備的關系

    /proc/devices/中的設備是通過insmod加載到內核的,它的主設備號(major)作為mknod的參數。 
/dev/*.* 是通過mknod加上去的,格式:mknod device c/b major minor 注意要在/dev目錄下建立設備節點,如:cd /dev 然后,mknod nrf24l01 c 238 0,用戶程序可通過設備名/dev/nrf24l01來調用驅動程序從而訪問硬件。 

--

使用awk工具可以從/proc/devices 文件中獲取設備號,建立一個.sh的腳本文件加入腳本:

module=xxx
major=`awk "\\$2==\"$module\" {print \\$1}" /proc/devices`
insmod xxx.ko
mknod /dev/xxx c $major 0

xxx為設備名

posted on 2011-01-05 23:24 小默 閱讀(2986) 評論(0)  編輯 收藏 引用 所屬分類: Linux

導航

統計

留言簿(13)

隨筆分類(287)

隨筆檔案(289)

漏洞

搜索

積分與排名

最新評論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            在线观看日韩一区| 欧美一区二区三区啪啪| 亚洲视频在线一区| 欧美视频一区在线观看| 亚洲一区二区三区国产| 久久久久久久欧美精品| 91久久精品美女高潮| 欧美日韩高清在线一区| 亚洲一区二区三区精品在线| 久久久av网站| 亚洲区中文字幕| 国产精品进线69影院| 欧美一区二区精品久久911| 欧美电影美腿模特1979在线看| 亚洲精品中文在线| 国产伦精品一区二区三区视频黑人| 欧美在线高清视频| 亚洲人成在线影院| 亚洲欧美日韩国产综合在线| 韩日欧美一区二区| 欧美日韩成人一区| 久久精品国产欧美激情| 亚洲精选视频在线| 久久综合色婷婷| 亚洲一区二区三区四区五区黄 | 韩国v欧美v日本v亚洲v| 免费看黄裸体一级大秀欧美| 在线亚洲观看| 欧美二区视频| 久久er99精品| 日韩午夜电影| 黄色另类av| 国产精品初高中精品久久| 老鸭窝毛片一区二区三区 | 欧美精选午夜久久久乱码6080| 亚洲一区二区三区四区五区午夜 | 欧美日韩国产在线播放| 欧美制服丝袜| 在线视频日韩| 亚洲日韩中文字幕在线播放| 久久婷婷国产麻豆91天堂| 亚洲素人一区二区| 亚洲日本激情| 精品粉嫩aⅴ一区二区三区四区| 国产精品伦一区| 欧美精品一区二区三区蜜桃| 久久女同精品一区二区| 欧美一区二区三区视频在线观看| 日韩视频欧美视频| 亚洲福利国产| 麻豆国产精品777777在线| 欧美综合国产精品久久丁香| 一区二区三区高清在线观看| 亚洲国产一区二区在线| 国模精品一区二区三区| 国产欧美日韩一区二区三区在线 | 国产一区二区三区免费不卡 | 久久久久久97三级| 亚洲欧美日韩精品一区二区| av成人免费在线| 亚洲精品乱码久久久久久黑人| **网站欧美大片在线观看| 国产午夜精品福利| 国产欧美一区二区三区久久| 国产精品久久久久久久久久久久| 欧美日韩一区二区在线视频| 欧美激情第9页| 欧美黄色视屏| 欧美精品在线观看一区二区| 欧美激情成人在线| 欧美激情亚洲视频| 欧美日韩国产综合久久| 欧美日韩在线播放| 欧美色欧美亚洲另类二区| 欧美日韩亚洲一区二| 欧美日韩免费一区| 国产精品jvid在线观看蜜臀| 国产精品h在线观看| 国产精品综合久久久| 国产精品永久免费| 国产一区美女| 亚洲国产精品va在看黑人| 亚洲丶国产丶欧美一区二区三区| 91久久精品国产91久久性色| 日韩亚洲精品视频| 亚洲综合精品自拍| 欧美一区二区三区在线播放| 久久成人资源| 欧美激情bt| 日韩视频一区二区三区| 亚洲欧美日韩国产综合在线| 久久精品国产精品| 欧美国产亚洲精品久久久8v| 欧美日韩在线免费视频| 国产女人aaa级久久久级| 在线成人亚洲| 一区二区三区**美女毛片| 午夜电影亚洲| 开心色5月久久精品| 亚洲欧洲美洲综合色网| 亚洲女同精品视频| 久久一区二区三区四区| 欧美日本韩国在线| 国产噜噜噜噜噜久久久久久久久| 狠狠色狠狠色综合| 一区二区三区高清在线观看| 久久精品日韩欧美| 亚洲丁香婷深爱综合| 亚洲一区二区三区欧美 | 欧美成人亚洲成人| 国产精品久久久久久亚洲毛片| 国精产品99永久一区一区| 99精品国产福利在线观看免费 | 久久偷窥视频| 欧美日韩免费一区二区三区| 国产一区二区精品久久91| 99精品免费| 久久免费午夜影院| 日韩亚洲欧美成人| 久久午夜电影网| 国产精品成人播放| 亚洲大胆美女视频| 欧美一区二区三区四区在线| 欧美国产日韩在线| 欧美亚洲视频在线观看| 欧美日韩dvd在线观看| 韩日欧美一区| 午夜精品免费| 日韩午夜高潮| 美女性感视频久久久| 国产视频精品免费播放| 一区二区久久久久| 蜜臀va亚洲va欧美va天堂 | 久久综合色综合88| 国产精品自在欧美一区| 一区二区高清在线| 欧美激情片在线观看| 久久av资源网| 国产精品欧美在线| 中文亚洲字幕| 亚洲黄色av一区| 裸体一区二区| 一区精品在线| 久久精品视频在线| 亚洲永久免费观看| 国产精品成人播放| 亚洲一区二区在线观看视频| 亚洲国产精品久久久| 久久在线免费观看| 精品电影在线观看| 久久综合伊人77777| 欧美一区影院| 国产日韩欧美高清免费| 午夜视频久久久| 一区二区三区四区五区精品| 欧美日韩国产首页| 在线亚洲一区| 亚洲三级视频在线观看| 欧美极品在线播放| 99国产精品视频免费观看| 亚洲啪啪91| 欧美揉bbbbb揉bbbbb| 一区二区三区成人| 一区二区三区视频观看| 国产精品va在线播放| 午夜精品久久久久久久久久久| 一区二区三区www| 国产精品久久久久久模特| 午夜欧美不卡精品aaaaa| 午夜精品视频| 韩国久久久久| 欧美高清在线视频| 欧美成人午夜激情| 这里只有精品视频| 亚洲一区二区三区四区五区黄| 国产亚洲精品久久久| 鲁大师成人一区二区三区| 老司机一区二区三区| 99一区二区| 宅男噜噜噜66一区二区| 国产日韩欧美成人| 欧美国产精品va在线观看| 欧美成人性生活| 亚洲免费网址| 久久精品国产免费看久久精品| 亚洲福利av| 99精品热视频| 国产区日韩欧美| 美女诱惑黄网站一区| 欧美久久一区| 久久成人精品一区二区三区| 久久成人精品一区二区三区| 亚洲日本理论电影| 亚洲女爱视频在线| 亚洲国产精品成人久久综合一区| 亚洲人成在线免费观看| 国产乱人伦精品一区二区| 欧美黄色影院| 国产精品乱码人人做人人爱| 老司机午夜免费精品视频|