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

小默

ldd3讀書(shū)筆記

Google 筆記本
ldd3讀書(shū)筆記

最近編輯過(guò)的 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)" : "");



網(wǎng)卡驅(qū)動(dòng)分析

---
1 找到網(wǎng)卡對(duì)應(yīng)驅(qū)動(dòng)模塊,測(cè)試

用lshw查看網(wǎng)卡對(duì)應(yīng)的驅(qū)動(dòng)。我的是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看到對(duì)應(yīng)模塊:
$lsmod

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

rmmod右上角網(wǎng)絡(luò)標(biāo)識(shí)圖成了叉叉。
insmod pcnet32.ko 網(wǎng)絡(luò)標(biāo)識(shí)圖重新連上。
(難道每次手動(dòng)停止/啟動(dòng)網(wǎng)絡(luò)連接,實(shí)際就是調(diào)用了卸載/加載模塊?囧)

--
2  嘗試編譯驅(qū)動(dòng)
把pcnet32.c拷貝出來(lái)。
掃了眼net下面的Makefile,300行,嚇?biāo)牢银B(niǎo)。
然后寫(xiě)了個(gè)4行的Makefile,居然編譯插入運(yùn)行一些正常,囧翻了。。。

--
3  分析代碼

--
4  分析在網(wǎng)絡(luò)模塊堆疊中的位置,數(shù)據(jù)流向

--
5  還沒(méi)有想好。。改下代碼?
2.6.20內(nèi)核以后的skbuff.h頭文件中將struct sk_buff結(jié)構(gòu)體修改了,h中包含有傳輸層的報(bào)文頭,nh中包含有網(wǎng)絡(luò)層的報(bào)文頭,而mac中包含的是鏈路層的報(bào)文頭。
linux-2.6.20以后的內(nèi)核頭文件sk_buff.h中這三個(gè)成員提取到單獨(dú)的變量對(duì)應(yīng)關(guān)系如下:
h-->transport_header;
nh-->network_header;
mac-->mac_header;
struct net


以前查過(guò)一次,還有一點(diǎn)印象
struct net是一個(gè)網(wǎng)絡(luò)名字空間namespace,在不同的名字空間里面可以有自己的轉(zhuǎn)發(fā)信息庫(kù),有自己的一套net_device等等。
默認(rèn)情況下都是使用init_net這個(gè)全局變量
Linux 系統(tǒng)內(nèi)核空間與用戶(hù)空間通信的實(shí)現(xiàn)與分析
https://www.ibm.com/developerworks/cn/linux/l-netlink/

推薦使用netlink套接字實(shí)現(xiàn)中斷環(huán)境和用戶(hù)態(tài)進(jìn)程通信。

CPU運(yùn)行狀態(tài)有4種:處理硬中斷;處理軟中斷;運(yùn)行于內(nèi)核態(tài),但有進(jìn)程上下文;運(yùn)行于用戶(hù)態(tài)進(jìn)程。
前三種狀態(tài),CPU運(yùn)行于內(nèi)核空間。

當(dāng)CPU運(yùn)行進(jìn)程上下文時(shí),可以被阻塞。這時(shí)可以通過(guò) copy_from_user()/copy_to_user() 實(shí)現(xiàn)內(nèi)核態(tài)和用戶(hù)態(tài)數(shù)據(jù)的拷貝。

當(dāng)CPU運(yùn)行于硬/軟中斷環(huán)境下時(shí),不可以被阻塞。這時(shí)有2種方法實(shí)現(xiàn)和用戶(hù)態(tài)的通信。
第1種方法:用一個(gè)和進(jìn)程上下文相關(guān)的內(nèi)核線程,完成從用戶(hù)態(tài)接收數(shù)據(jù)的任務(wù);再通過(guò)一個(gè)臨界區(qū)將數(shù)據(jù)傳給中斷過(guò)程。缺點(diǎn):中斷過(guò)程不能實(shí)時(shí)接收來(lái)自用戶(hù)態(tài)進(jìn)程的數(shù)據(jù)。
第2中方法:使用netlink套接字。用一個(gè)軟中斷調(diào)用用戶(hù)實(shí)現(xiàn)指定的接收函數(shù);再通過(guò)臨界區(qū)將數(shù)據(jù)傳給中斷過(guò)程。因?yàn)槭褂密浿袛鄟?lái)接收數(shù)據(jù),所以可以保證數(shù)據(jù)接收的實(shí)時(shí)性。
Q:軟中斷不可以被阻塞,怎么從用戶(hù)空間接收數(shù)據(jù)。

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  /*用戶(hù)空間進(jìn)程ID*/
#define IMP2_K_MSG 1  /*內(nèi)核發(fā)出的消息*/
#define IMP2_CLOSE 2  /*用戶(hù)空間進(jìn)程關(guān)閉*/

#define NL_IMP2  31  /*自定義的netlink協(xié)議類(lèi)型*/

/*nlmsghdr 后的數(shù)據(jù)*/
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;

/*信號(hào)SIGINT的處理函數(shù)-告訴內(nèi)核用戶(hù)端關(guān)閉*/
static void sig_int(int signo)
{
struct sockaddr_nl kpeer;
struct msg_to_kernel message;

/*內(nèi)核端地址*/
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;

/*創(chuàng)建套接字,設(shè)置用戶(hù)端地址,綁定套接字和地址*/
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
/*netlink套接字,用戶(hù)層端地址*/
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;
}

/*設(shè)置SIGINT信號(hào)的處理函數(shù)為sig_int*/
signal(SIGINT, sig_int);

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

/*把用戶(hù)端進(jìn)程的pid傳遞給內(nèi)核*/
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));

/*循環(huán)接收內(nèi)核發(fā)來(lái)的數(shù)據(jù)*/
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點(diǎn)截獲ICMP數(shù)據(jù)報(bào),記錄源地址和目錄地址
 * 使用netlink套接字將信息傳遞給用戶(hù)進(jìn)程,再由用戶(hù)進(jìn)程打印到終端上
 */
 
#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的接收回調(diào)函數(shù),負(fù)責(zé)接收用戶(hù)層端的數(shù)據(jù)。運(yùn)行在軟中斷環(huán)境*/
static void kernel_receive(struct sk_buff *_skb)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;

if(down_trylock(&receive_sem))
return;

/*檢查數(shù)據(jù)合法性*/
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)){
/*用戶(hù)層端進(jìn)程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);
}
/*用戶(hù)端進(jìn)程關(guān)閉,置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的用戶(hù)端發(fā)送數(shù)據(jù)*/
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)); /*字節(jié)對(duì)齊后的數(shù)據(jù)報(bào)長(zhǎng)度*/

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

/*填寫(xiě)數(shù)據(jù)報(bào)相關(guān)信息*/
nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));

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

/*計(jì)算經(jīng)過(guò)字節(jié)對(duì)其后nlmsg的長(zhǎng)度*/
nlh->nlmsg_len = skb->tail - old_tail;
NETLINK_CB(skb).dst_group = 0;

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

return ret;

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

return -1;
}

/* netfilter NF_IP_PRE_ROUTING點(diǎn)的掛接函數(shù)
 * 截獲ip數(shù)據(jù)報(bào)
 */
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);
/*如果傳輸層協(xié)議是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); /*將數(shù)據(jù)發(fā)送給netlink的用戶(hù)層端*/
}
else
read_unlock_bh(&user_proc.lock);
}

return NF_ACCEPT;
}

/*給netfilter框架的NF_IP_PRE_ROUTING點(diǎn)上掛接函數(shù)get_icmp()*/
static struct nf_hook_ops imp2_ops = {
.hook = get_icmp, /*hook被調(diào)用時(shí)執(zhí)行的回調(diào)函數(shù)*/
.pf = PF_INET, /*協(xié)議族,<linux/socket.h>。ipv4使用PF_INET*/
.hooknum = 0, /*安裝的這個(gè)函數(shù),對(duì)應(yīng)具體的hook類(lèi)型*/
.priority = NF_IP_PRI_FILTER - 1,
};

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

/* 創(chuàng)建netlink套接字,指明接收數(shù)據(jù)的回調(diào)函數(shù)kernel_receive()
 * 這里協(xié)議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;
}

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

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

module_init(init);
module_exit(fini);

at the beginning

# 一個(gè)驅(qū)動(dòng)程序的角色是提供機(jī)制,而不是策略 2010-11-15 09:26 小默

“一個(gè)驅(qū)動(dòng)程序的角色是提供機(jī)制,而不是策略。”--ldd3

機(jī)制:提供什么能力
策略:如何使用這些能力
機(jī)制和策略由軟件不同部分,或完全不同的軟件實(shí)現(xiàn)。

比如第一次實(shí)習(xí)時(shí):
我們這邊負(fù)責(zé)寫(xiě)驅(qū)動(dòng),只關(guān)注實(shí)現(xiàn)什么功能,怎么實(shí)現(xiàn)這樣功能,這是機(jī)制。我們可以直接在設(shè)備管理器中安裝卸載,或者用命令行安裝卸載使用等,隨意,也就是開(kāi)發(fā)過(guò)程完全不考慮策略。
等開(kāi)發(fā)進(jìn)行到了一定階段,又招了另外一名同學(xué)負(fù)責(zé)界面,這是策略。用戶(hù)怎么使用這個(gè)驅(qū)動(dòng),操作界面是怎樣的,是由他來(lái)負(fù)責(zé)的。

不知道是不是這個(gè)意思O(∩_∩)O~~  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# makefile寫(xiě)法 2010-11-15 17:14 小默

對(duì)于單個(gè).c文件的hello world例子:
obj-m := hello.o
從目標(biāo)文件hello.o簡(jiǎn)歷一個(gè)模塊hello.ko

如果模塊來(lái)自?xún)蓚€(gè)源文件file1.c和file2.c:
obj-m := module.o
module-objs := file1.o file2.o

上面的命令,必須在內(nèi)核系統(tǒng)上下建立文中被調(diào)用

-----

在任意當(dāng)前工作目錄中,需要在makefile中指明源碼樹(shù)的路徑。

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

創(chuàng)建一個(gè)模塊需要調(diào)用兩次上面的makefile
第一次:沒(méi)有設(shè)置變量KERNELRELEASE,通過(guò)已安裝模塊目錄中的符號(hào)鏈接指回內(nèi)核建立樹(shù);然后運(yùn)行default中的make命令,第二次使用makefile
第二次:已經(jīng)設(shè)置了變量KERNELRELEASE,直接obj-m := hello.o創(chuàng)建模塊  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

modprobe實(shí)現(xiàn)和insmod一樣加載模塊到內(nèi)核的功能
不同的是,加載前會(huì)檢查模塊中是否有當(dāng)前內(nèi)核中沒(méi)有定義的symbol,如果有,在模塊搜索路徑中尋找其它模塊是否含有上面的symbol,有的話,自動(dòng)加載關(guān)聯(lián)模塊
insmod對(duì)于這種情況,會(huì)報(bào)錯(cuò)unresolved symbols

查看當(dāng)前加載模塊:
lsmod
cat /proc/modules
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# 模塊參數(shù) 2010-11-15 18:40 小默

說(shuō)10次hello,Mom
# insmod hellop howmany=10 whom="Mom"

--

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

--

S_IRUGO 允許所有人讀
S_IRUGO | S_IWUSR 允許所有人讀,允許root改變參數(shù)

如果參數(shù)被sysfs修改,不會(huì)通知模塊。不要使參數(shù)可寫(xiě),除非準(zhǔn)備好檢測(cè)參數(shù)改變。

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

# 設(shè)備主次編號(hào) 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的是字符設(shè)備,第一列b塊設(shè)備

修改日期前的兩個(gè)數(shù)字。
第一個(gè)是主設(shè)備編號(hào):標(biāo)識(shí)設(shè)備相連的驅(qū)動(dòng)
第二個(gè)是次設(shè)備編號(hào):決定引用哪個(gè)設(shè)備

-------
設(shè)備編號(hào)的內(nèi)部表示

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

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

將主次編號(hào)轉(zhuǎn)換成dev_t:
MKDEV(int major, int minor);

--------
分配和釋放設(shè)備編號(hào)

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

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

設(shè)備編號(hào)的釋放:
void unregister_chrdev_region(dev_t first, unsigned int count);  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# 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 # 插入模塊,使用獲取的所有參數(shù)($*)

10 # remove stale nodes刪除無(wú)效的節(jié)點(diǎn),不能刪除device0,device1,device2...阿。。TODO
11 rm -f /dev/${device}[0-3]
12 
13 major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #TODO 沒(méi)有搞明白
14 mknod /dev/${device}0 c $major 0 #創(chuàng)建4個(gè)虛擬設(shè)備
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 # 改變?cè)O(shè)備的組和模式。腳本必須以root運(yùn)行,但設(shè)備使用可能需要其它用戶(hù)寫(xiě)。
24 chgrp $group /dev/${device}[0-3]
25 chmod $mode /dev/${device}[0-3]
26 
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# file_operations結(jié)構(gòu) 2010-11-16 15:24 小默

一些處理文件的回調(diào)函數(shù)

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 };  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# 注冊(cè)字符設(shè)備 2010-11-16 15:25 小默

12 // 使用struct scull_dev結(jié)構(gòu)表示每個(gè)設(shè)備
13 // TODO 沒(méi)有理解什么意思
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; // 字符設(shè)備結(jié)構(gòu)
22 };
23 
24 // 初始化struct cdev,并添加到系統(tǒng)中
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 初始化已經(jīng)分配的結(jié)構(gòu). 不是很理解
30 // cdev結(jié)構(gòu)嵌套在struct scull_dev中,必須調(diào)用cdev_init()來(lái)初始化cdev結(jié)構(gòu)
31 cdev_init(&dev->cdev, &scull_fops);
32 dev->cdev.owner = THIS_MODULE;
33 dev->cdev.ops = &scull_fops;
34 // 添加到系統(tǒng)中
35 err = cdev_add(&dev->cdev, devno, 1);
36 if(err)
37 printk(KERN_NOTICE "Error %d adding scull%d", err, index);
38 }
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

system-config-selinux
什么時(shí)候改了,汗 //TODO  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

// 表示每個(gè)設(shè)備
struct scull_dev{
struct scull_qset *data; // pointer to first quantum set
int quantum; // the current quantum size - 當(dāng)前量子和量子集大小
int qset; // the current array size - 每個(gè)內(nèi)存區(qū)域?yàn)橐粋€(gè)量子,數(shù)組為一個(gè)量子集
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
};

// 量子集,即一個(gè)內(nèi)存區(qū)域的數(shù)組
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; // 量子集中的第一個(gè)元素
int quantum = dev->quantum, qset = dev->qset; // 當(dāng)前量子和量子集大小
int itemsize = quantum * qset; // listitem中的字節(jié)數(shù)=量子大小*量子集大小
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到右側(cè)
dptr = scull_follow(dev, item); // 量子集中的第一個(gè)元素
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;

// 只讀取到這個(gè)量子的尾部
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;
}

// 一次處理單個(gè)量子
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到右側(cè)
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;
}

// 只寫(xiě)到這個(gè)量子的結(jié)束
if(count > quantum-q_pos)
count = quantum - q_pos;
// 從用戶(hù)空間拷貝一整段數(shù)據(jù)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輪流讀取指示的數(shù)量到每個(gè)緩存;writev收集每個(gè)緩存的內(nèi)容到一起并且作為單個(gè)寫(xiě)操作送出它們。
// count參數(shù)告訴有多少iovec結(jié)構(gòu),這些結(jié)構(gòu)由應(yīng)用程序創(chuàng)建,但是內(nèi)核在調(diào)用驅(qū)動(dòng)之前拷貝它們到內(nèi)核空間。
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描述了一塊要傳送的數(shù)據(jù)
struct iovec{
void __user *iov_base; // 開(kāi)始于iov_base(在用戶(hù)空間)
__kernel_size_t iov_len; // 并有iov_len長(zhǎng)
};
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

# 重定向控制臺(tái)消息 2010-11-30 22:44 小默

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

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傳遞一個(gè)指向字節(jié)數(shù)組的指針作為參數(shù),數(shù)組的第一個(gè)字節(jié)是一個(gè)數(shù)(需要指定的子命令)。
// 當(dāng)子命令是11時(shí),下一個(gè)字節(jié)指定虛擬控制臺(tái)。
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);
}
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

// 在proc里實(shí)現(xiàn)文件,在文件被讀時(shí)產(chǎn)生數(shù)據(jù)。
// 當(dāng)一個(gè)進(jìn)程讀你的/proc文件,內(nèi)核分配了一頁(yè)內(nèi)存,驅(qū)動(dòng)可以寫(xiě)入數(shù)據(jù)返回給用戶(hù)空間。
// buf 寫(xiě)數(shù)據(jù)的緩沖區(qū);start有關(guān)數(shù)據(jù)寫(xiě)在頁(yè)中哪里;eof必須被驅(qū)動(dòng)設(shè)置,表示寫(xiě)數(shù)據(jù)結(jié)束;data用來(lái)傳遞私有數(shù)據(jù)。
// 假定不會(huì)有必要產(chǎn)生超過(guò)一頁(yè)的數(shù)據(jù),并且因此忽略了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;
// 設(shè)備號(hào),量子集大小,量子大小,存儲(chǔ)的數(shù)據(jù)量
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 顯示一個(gè)指針
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; // 返回實(shí)際在頁(yè)中寫(xiě)了多少數(shù)據(jù)
}


/// 移除entry的一些問(wèn)題
// 移除可能發(fā)生在文件正在被使用時(shí)。/proc入口沒(méi)有owner,沒(méi)有引用計(jì)數(shù)。
// 內(nèi)核不檢查注冊(cè)的名字是否已經(jīng)存在,可能會(huì)有多個(gè)entry使用相同名稱(chēng)。而且在訪問(wèn)和remove_proc_entry時(shí),它們沒(méi)有區(qū)別。。。悲劇。。。  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

// 創(chuàng)建一個(gè)虛擬文件,遍歷一串?dāng)?shù)據(jù),這些數(shù)據(jù)必須返回用戶(hù)空間。
// start, next, stop, show

// sfile 總被忽略;pos指從哪兒開(kāi)始讀,具體意義完全依賴(lài)于實(shí)現(xiàn)。
// seq_file典型的實(shí)現(xiàn)是遍歷一感興趣的數(shù)據(jù)序列,pos就用來(lái)指示序列中的下一個(gè)元素。
// 在scull中,pos簡(jiǎn)單地作為scull_array數(shù)組的索引。
// 原型
void *start(struct seq_file *sfile, loff_t *pos);
// 在scull中的實(shí)現(xiàn)
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; // 返回供迭代器使用的私有數(shù)據(jù)
}

// next把迭代器后挪一位,返回NULL表示沒(méi)有更多數(shù)據(jù)了
// v:上一次start/next調(diào)用返回的迭代器 TODO ???返回的不是私有數(shù)據(jù)么?
// pos: 文件中的當(dāng)前位置。
void *next(struct seq_file *sfile, void *v, loff_t *pos);
// scull的實(shí)現(xiàn)
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;
}

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

// start到stop期間不會(huì)有sleep或者非原子操作,可以放心的在start中獲得信號(hào)量或自旋鎖。整個(gè)調(diào)用序列都是原子的。天書(shū)啊 TODO ???

// 在start和stop期間,內(nèi)核調(diào)用show輸出迭代器v生成的數(shù)據(jù)到用戶(hù)空間
int show(struct seq_file *sfile, void *v);
// 輸出,等效于用戶(hù)空間的printf。返回非0值表示緩沖滿,輸出的數(shù)據(jù)會(huì)被丟棄。不過(guò)大多數(shù)實(shí)現(xiàn)都忽略返回值。
int seq_sprintf(struct seq_file *sfile, const char *fmt, ...);
// 等效于用戶(hù)空間的putc和puts
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);
// 如果s中有esc中的數(shù)據(jù),這些數(shù)據(jù)用8進(jìn)制輸出。常見(jiàn)的esc是"\t\n\\",用于保持空格,避免搞亂輸出。
int seq_escape(struct seq_file *m, const char *s, const char *esc);
// scull中show實(shí)現(xiàn)
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的一個(gè)指針,囧。。。
// 迭代器操作集
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結(jié)構(gòu)結(jié)構(gòu),實(shí)現(xiàn)內(nèi)核read/seek文件的所有操作。
// 創(chuàng)建一個(gè)open方法,連接文件和seq_file操作 TODO 沒(méi)看懂
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結(jié)構(gòu)
static struct fle_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};

// 在/proc中創(chuàng)建設(shè)備
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);  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

略 O(∩_∩)O~~  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

講了兩部分

一、system opps
空指針引用,或者局部變量賦值覆蓋了原eip,導(dǎo)致頁(yè)錯(cuò)誤

二、系統(tǒng)掛起 // TODO 看得云里霧里的
死循環(huán)等引起
假掛起:鼠標(biāo)鍵盤(pán)等外設(shè)沒(méi)有響應(yīng)了,系統(tǒng)實(shí)際正常。可以看時(shí)間。。。
插入schedule調(diào)用防止死循環(huán),作用是允許其他進(jìn)程從當(dāng)前進(jìn)程竊取時(shí)間。講了些弊端,沒(méi)看懂
sysrq:沒(méi)看懂
  回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

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

原子類(lèi)型定義:
typedef struct
{
volatile int counter;
}
atomic_t; 

volatile修飾字段告訴gcc不要對(duì)該類(lèi)型的數(shù)據(jù)做優(yōu)化處理,對(duì)它的訪問(wèn)都是對(duì)內(nèi)存的訪問(wèn),而不是對(duì)寄存器的訪問(wèn)。  回復(fù)  更多評(píng)論刪除評(píng)論  修改評(píng)論

# 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;  回復(fù)  更多評(píng)論 <a id="AjaxHolder_Comments_CommentList_ctl24_DeleteLink"

ch03 字符驅(qū)動(dòng)

3.4 字符設(shè)備注冊(cè)

struct cdev  <linux/cdev.h>

1. 分配并初始化一個(gè)cdev結(jié)構(gòu)。

如果期望獲得一個(gè)獨(dú)立的cdev結(jié)構(gòu):

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


如果想把cdev嵌入到自定義的設(shè)備結(jié)構(gòu)中:

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


上面兩種方法,都需要初始化owner成員為T(mén)HIS_MODULE

2.把新建立的cdev結(jié)構(gòu)告訴內(nèi)核

// 把已經(jīng)分配、初始化的cdev結(jié)構(gòu)通知內(nèi)核。立即生效。出錯(cuò)時(shí)返回一個(gè)負(fù)的錯(cuò)誤碼。
int cdev_add(struct cdev *dev, 
               dev_t num,           
// 設(shè)備響應(yīng)的第一個(gè)設(shè)備號(hào)
               unsigned int count);  // 關(guān)聯(lián)到設(shè)備上的設(shè)備號(hào)數(shù)量


3.移除cdev結(jié)構(gòu)

// 從系統(tǒng)中移除一個(gè)字符設(shè)備
void cdev_del(struct cdev *dev);


scull中字符設(shè)備注冊(cè)

 // 注冊(cè)字符設(shè)備
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    
int err, devno = MKDEV(scull_major, scull_minor + index); // MKDEV 把主次設(shè)備號(hào)合成為一個(gè)dev_t結(jié)構(gòu)

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




// 注冊(cè)字符設(shè)備過(guò)去的方法
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方法在驅(qū)動(dòng)中用來(lái)做一些初始化準(zhǔn)備工作。

在多數(shù)驅(qū)動(dòng)中,open進(jìn)行的工作:
- 檢查設(shè)備特定的錯(cuò)誤(device-not-ready等)
- 如果設(shè)備第一次打開(kāi),初始化設(shè)備
- 更新 f_op 指針,如果需要的話
- 給 filp->private_data 賦值

open原型:

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

如何確定打開(kāi)的是哪個(gè)設(shè)備:
- inode中i_cdev成員,是之前建立的cdev結(jié)構(gòu)。通常我們需要的不是cdev,而是包含cdev的scull_dev結(jié)構(gòu),這時(shí)使用    
container_of(pointer, container_type, container_field);
- 查看存儲(chǔ)在inode中的次設(shè)備號(hào)。如果使用register_chrdev注冊(cè)設(shè)備,必須采用這種方法。

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

    
// 文件私有數(shù)據(jù),設(shè)置成對(duì)應(yīng)的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 */
    
// 文件以只讀模式打開(kāi)時(shí),截?cái)酁?
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        
if (down_interruptible(&dev->sem)) // 申請(qǐng)新號(hào)量,可中斷
            return -ERESTARTSYS;
        
// 釋放整個(gè)數(shù)據(jù)區(qū)
        scull_trim(dev); /* ignore errors */ 
        up(
&dev->sem);  // 釋放信號(hào)量
    }
    
return 0;          /* success */
}

3.5.2 release方法

release進(jìn)行的工作:
- 釋放open在file->private_data中申請(qǐng)的一切
- 在調(diào)用最后一個(gè)close時(shí)關(guān)閉設(shè)備

scull的基本形式?jīng)]有需要關(guān)閉的設(shè)備。
scull_release:
int scull_release(struct inode *inode, struct file *filp)
{
    
return 0;
}


close 和 release
并不是每一個(gè)close系統(tǒng)調(diào)用,都會(huì)引起對(duì)release的調(diào)用。
內(nèi)核對(duì)一個(gè)打開(kāi)的file結(jié)構(gòu),維護(hù)一個(gè)引用計(jì)數(shù)。dup和fork都只遞增現(xiàn)有file結(jié)構(gòu)的引用計(jì)數(shù)。只有當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)close的調(diào)用才引起對(duì)release的調(diào)用。
3.6 scull的內(nèi)存使用 

how & why 執(zhí)行內(nèi)存分配
scull 使用的內(nèi)存區(qū)(被稱(chēng)為device)長(zhǎng)度可變,隨寫(xiě)的內(nèi)容增多而增長(zhǎng);修剪通過(guò)用短文件重寫(xiě)device實(shí)現(xiàn)。

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

kmalloc不是申請(qǐng)大塊內(nèi)存的有效方法,但為了代碼簡(jiǎn)單易懂,在這里暫時(shí)使用kmalloc。另外,我們沒(méi)有限制“device”區(qū)域大小:理論上不該給數(shù)據(jù)施加武斷的限制;實(shí)踐上scull可以暫時(shí)吃光所有系統(tǒng)內(nèi)存來(lái)模擬低內(nèi)存環(huán)境下的測(cè)試。可以使用 cp /dev/zero /dev/scull0 吃光所有RAM內(nèi)存,也可以用 dd 工具指定分配給scull的內(nèi)存。

2. scull device結(jié)構(gòu):
在scull中,每個(gè)device是一系列指向scull_device結(jié)構(gòu)的指針組成的鏈表。每個(gè)scull_device結(jié)構(gòu)默認(rèn)指向最多4M的內(nèi)存區(qū):有多個(gè)量子集,每個(gè)量子集有多個(gè)量子。每個(gè)scull_device的布局如下:


struct scull_qset {
    void **data;
    struct scull_qset *next;
};
3. 量子和量子集大小設(shè)置:
在scull中單寫(xiě)一個(gè)字節(jié)需要8000kb或1200kb內(nèi)存:量子用掉4000;量子集用掉4000kb或8000kb(根據(jù)在目標(biāo)平臺(tái)上一個(gè)指針代碼32位或者64位)。但是,當(dāng)寫(xiě)入數(shù)據(jù)量比較大時(shí),鏈表的開(kāi)銷(xiāo)還可以:每4M字節(jié)一個(gè)鏈表。

為量子和量子集選擇合適的值是策略問(wèn)題,而不是機(jī)制。scull驅(qū)動(dòng)不應(yīng)該強(qiáng)制使用特定值,而是可以由用戶(hù)設(shè)置:編譯時(shí)改變scull.h中宏SCULL_QUANTUM和SCULL_QSET;模塊加載時(shí)設(shè)置scull_quantum和scull_qset;運(yùn)行時(shí)通過(guò)ioctl改變。

4. scull中的內(nèi)存管理的代碼段

// 釋放整個(gè)數(shù)據(jù)區(qū)。簡(jiǎn)單遍歷列表并且釋放它發(fā)現(xiàn)的任何量子和量子集。
// 在scull_open 在文件為寫(xiě)而打開(kāi)時(shí)調(diào)用。
// 調(diào)用這個(gè)函數(shù)時(shí)必須持有信號(hào)量。
/*
 * 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;

    // 遍歷多個(gè)量子集。dev->data 指向第一個(gè)量子集。
    for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
        if (dptr->data) { // 量子集中有數(shù)據(jù)
            for (i = 0; i < qset; i++) // 遍歷釋放當(dāng)前量子集中的每個(gè)量子。量子集大小為qset。 
                kfree(dptr->data[i]);
            kfree(dptr->data); // 釋放量子指針數(shù)組
            dptr->data = NULL; 
        }
        // next獲取下一個(gè)量子集,釋放當(dāng)前量子集。
        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進(jìn)行在內(nèi)核和用戶(hù)空間之間復(fù)制數(shù)據(jù)的工作。
原型:
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是要讀寫(xiě)字節(jié)數(shù)。buff指向用戶(hù)空間地址。offp是指向long offset type對(duì)象的指針,指明要訪問(wèn)的文件位置。
返回實(shí)際傳輸?shù)淖止?jié)數(shù),出錯(cuò)返回負(fù)值。

訪問(wèn)用戶(hù)空間地址char __user *buff:

驅(qū)動(dòng)中不應(yīng)該直接對(duì)用戶(hù)空間地址指針 *buff 解引用。原因:
- 用戶(hù)空間地址指針在內(nèi)核中可能根本無(wú)效,這取決于主機(jī)體系結(jié)構(gòu)和內(nèi)核配置。
- 即便這個(gè)指針在內(nèi)核中代表同一個(gè)東西,但用戶(hù)空間是分頁(yè)的,地址可能不在RAM中。直接解引用 用戶(hù)空間地址指針 可能引發(fā)頁(yè)錯(cuò)誤。
- 用戶(hù)程序提供的指針可能是錯(cuò)誤的或者惡意的。盲目解引用用戶(hù)提供的指針,將允許用戶(hù)訪問(wèn)系統(tǒng)內(nèi)存中任何地方。

應(yīng)該通過(guò)內(nèi)核提供的函數(shù)訪問(wèn)用戶(hù)空間。
scull中復(fù)制一整段數(shù)據(jù),使用下面兩個(gè)函數(shù):
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);
使用上面兩個(gè)函數(shù)需要注意的一點(diǎn):當(dāng)用戶(hù)空間地址不在內(nèi)存中時(shí),進(jìn)程會(huì)投入睡眠等待內(nèi)存調(diào)度,所以任何存取用戶(hù)空間的函數(shù)必須是可重入的。
數(shù)據(jù)傳送完成后需要修改*offp為當(dāng)前的文件位置。
read參數(shù):

讀device時(shí)需要考慮的特殊情況:當(dāng)前讀位置大于設(shè)備大小

這種情況發(fā)生在:進(jìn)程a在讀設(shè)備,同時(shí)進(jìn)程b寫(xiě)設(shè)備將長(zhǎng)度截?cái)酁?。此時(shí)scull的read返回0表示已到文件尾(沒(méi)有更多的數(shù)據(jù)可以讀)

ch04 調(diào)試技術(shù)

4.1 內(nèi)核中的調(diào)試支持

編譯內(nèi)核時(shí),"kernel hacking"菜單中有一些調(diào)試選項(xiàng)。
書(shū)中列了一些,暫時(shí)沒(méi)有看
4.2 debugging by print

掃下目錄,暫時(shí)沒(méi)有興趣細(xì)看o(╯□╰)o

4.2.1 printk
4.2.2 重定向控制臺(tái)消息
4.2.3 消息是如何被記錄的
4.2.4 關(guān)閉或打開(kāi)消息
4.2.5 速率限制
4.2.6 打印設(shè)備號(hào) 
4.3-4.6 心情不好,不想看鳥(niǎo)~

ch05 并發(fā)和競(jìng)爭(zhēng)

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

競(jìng)爭(zhēng)條件是伴隨著資源的共享訪問(wèn)而來(lái)的。內(nèi)核代碼應(yīng)該有最小的共享,其中最明顯的應(yīng)用就是盡可能少地使用全局變量。

共享無(wú)處不在。硬件是共享的,軟件資源常常需要共享給多個(gè)線程。需要牢記全局變量遠(yuǎn)非唯一的共享方式。當(dāng)把指針傳遞到內(nèi)核其他部分時(shí),可能就創(chuàng)建了一個(gè)新的共享。

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

另外一個(gè)重要的規(guī)則:如果內(nèi)核代碼創(chuàng)建了一個(gè)可能被內(nèi)核其它部分共享的對(duì)象,這個(gè)對(duì)象必須一直存在直到?jīng)]有外部引用。這里有2層意思:只有做好所有準(zhǔn)備后才能告訴內(nèi)核設(shè)備存在了;必須記錄共享對(duì)象的引用數(shù)。
5.3 信號(hào)量和互斥

我們的目的是對(duì)scull數(shù)據(jù)結(jié)構(gòu)的操作是原子的(atomic):也就是說(shuō)在有其它線程會(huì)影響到這個(gè)數(shù)據(jù)結(jié)構(gòu)前,操作必須一次完成。為此需要設(shè)置一個(gè)臨界區(qū)(critical sections):其中的代碼在任何時(shí)刻只能由一個(gè)線程執(zhí)行。

內(nèi)核提供了多種原語(yǔ)來(lái)滿足不同臨界區(qū)的要求。在這個(gè)例子中,scull驅(qū)動(dòng)可以睡眠等待:只有用戶(hù)的直接請(qǐng)求會(huì)產(chǎn)生對(duì)scull數(shù)據(jù)的訪問(wèn),不會(huì)有來(lái)自中斷處理或者異步上下文中的訪問(wèn),另外scull也沒(méi)有持有關(guān)鍵的系統(tǒng)資源。所以可以使用鎖機(jī)制:可能導(dǎo)致進(jìn)程在等待訪問(wèn)臨界區(qū)時(shí)睡眠。

另外很重要的一點(diǎn),我們?cè)谂R界區(qū)中將執(zhí)行另外一個(gè)可能會(huì)導(dǎo)致線程休眠的操作:kmalloc申請(qǐng)內(nèi)存。所以我們選用的鎖原語(yǔ),必須能夠在擁有鎖的線程休眠時(shí)正常工作。在這里最合適的機(jī)制是信號(hào)量。

一個(gè)信號(hào)量本質(zhì)上是一個(gè)整數(shù)值,它和一對(duì)PV函數(shù)混合使用。希望進(jìn)入臨界區(qū)的線程在相關(guān)信號(hào)量上調(diào)用P函數(shù):如果信號(hào)量值>0,值減1,,線程繼續(xù);否則線程休眠等待其它人釋放信號(hào)量。用V解鎖信號(hào)量:增加信號(hào)量值,并在必要時(shí)喚醒等待線程。

信號(hào)量的初始值表示總共有幾個(gè)資源可供同時(shí)使用。當(dāng)信號(hào)量用于互斥時(shí)(mutual exclusion,避免多個(gè)線程同時(shí)在一個(gè)臨界區(qū)中運(yùn)行),信號(hào)量的值應(yīng)該初始化為1。這時(shí),信號(hào)量也常常稱(chēng)為互斥體。

訪問(wèn)臨界區(qū)的原語(yǔ)
| 鎖:可能導(dǎo)致進(jìn)程休眠。
| | 信號(hào)量:能夠在擁有鎖的線程休眠時(shí)正常工作。
| | | 互斥體:初始化為1的信號(hào)量,用戶(hù)互斥。

5.3.1 Linux 信號(hào)量的實(shí)現(xiàn)

<linux/semaphore.h>
struct semaphore

信號(hào)量有多種聲明和初始化的方法。

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

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

void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
用于互斥體必須在運(yùn)行時(shí)初始化,例如在動(dòng)態(tài)分配互斥體的情況下。

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

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

5.3.2 在scull中使用信號(hào)量

使用鎖原語(yǔ)的關(guān)鍵:明確要保護(hù)的資源;確保對(duì)該資源的每次訪問(wèn)都使用了合適的鎖原語(yǔ)。

在scull驅(qū)動(dòng)中,要限制訪問(wèn)的資源都在scull_dev結(jié)構(gòu)中。我們?yōu)槊總€(gè)scull設(shè)備使用一個(gè)單獨(dú)的信號(hào)量:沒(méi)有理由讓一個(gè)進(jìn)程在其他進(jìn)程訪問(wèn)別的設(shè)備時(shí)等待;允許在不同設(shè)備上的操作并行進(jìn)行。

信號(hào)量必須在scull設(shè)備對(duì)系統(tǒng)中其他部分可用前被初始化。我們?cè)趕cull_init_module中,在調(diào)用scull_setup_cdev(向內(nèi)核注冊(cè)設(shè)備)前調(diào)用了init_MUTEX。
必須仔細(xì)檢查代碼,確保在不擁有該信號(hào)量時(shí)不訪問(wèn)scull_dev結(jié)構(gòu)。例如,在scull_write開(kāi)始處先申請(qǐng)dev->sem。
最后,scull_write必須釋放信號(hào)量。

5.3.3 Reader/Writer 信號(hào)量

semaphore對(duì)所有要訪問(wèn)數(shù)據(jù)的線程互斥,但實(shí)際上多個(gè)線程同時(shí)讀數(shù)據(jù)不會(huì)出錯(cuò)。

rwsem允許一個(gè)線程寫(xiě)數(shù)據(jù) 或者 多個(gè)線程讀數(shù)據(jù),寫(xiě)優(yōu)先級(jí)大于讀。也就是說(shuō),讀寫(xiě)不能同時(shí)進(jìn)行,讀寫(xiě)同時(shí)申請(qǐng)rwsem時(shí),寫(xiě)優(yōu)先獲得。結(jié)果是,如果有大量線程等待寫(xiě)數(shù)據(jù),可能讀申請(qǐng)很久都得不到滿足,餓死。所以,最好在很少需要寫(xiě)數(shù)據(jù),并且寫(xiě)數(shù)據(jù)用時(shí)很短時(shí)使用rwsem。

下面是使用rwsem的接口:

<linux/rwsem.h>
struct rw_semaphore

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

void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);
down_read可能會(huì)使進(jìn)程進(jìn)入不可中斷的休眠。
down_read_trylock在獲得訪問(wèn)權(quán)限時(shí)返回非0,其它情況下返回0。 --注意返回值和一般內(nèi)核函數(shù)不同:一般內(nèi)核函數(shù)出錯(cuò)返回非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);
在結(jié)束修改后調(diào)用downgrage_write,允許其它讀用戶(hù)訪問(wèn)。
5.4 complete 機(jī)制

問(wèn)題提出:

內(nèi)核編程中經(jīng)常遇到一種情況,啟動(dòng)當(dāng)前線程外部進(jìn)行一項(xiàng)任務(wù),并等待它完成。例如:創(chuàng)建一個(gè)新的內(nèi)核線程或用戶(hù)空間進(jìn)程;對(duì)一個(gè)已有進(jìn)程的某個(gè)請(qǐng)求;某個(gè)硬件動(dòng)作。

這時(shí)可以使用信號(hào)量來(lái)同步這兩個(gè)任務(wù):start外部任務(wù)后馬上down(sem);當(dāng)外部任務(wù)完成工作時(shí)up(sem)。
    struct semphore sem;

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

    當(dāng)外部任務(wù)完成工作時(shí),up(&sem);

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

解決方法:

complete機(jī)制,允許一個(gè)線程告訴另一個(gè)線程某項(xiàng)工作已完成。

<linux/completion.h>
struct completion

DECLARE_COMPLETION(my_completion);
創(chuàng)建completion。

struct completion my_completion;
/* .. */
init_completion(&my_completion);
動(dòng)態(tài)的創(chuàng)建和初始化completion。

void wait_for_completion(struct completion *c);
等待completion。該函數(shù)執(zhí)行的是非中斷等待,如果沒(méi)有人完成該任務(wù),則將產(chǎn)生一個(gè)不可殺進(jìn)程。

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

completion機(jī)制的典型應(yīng)用:內(nèi)核模塊exit時(shí)等待線程終止
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在等在時(shí)有什么區(qū)別?為什么后者效率就高了?
5.5 自旋鎖

自旋鎖可在不能休眠的代碼中使用,如中斷處理例程。 // TODO:休眠和自旋 有什么區(qū)別??jī)?yōu)劣之處?

一個(gè)自旋鎖是一個(gè)互斥設(shè)備,只能有兩個(gè)值:locked和unlocked。它通常是一個(gè)int值的單個(gè)bit。申請(qǐng)鎖的代碼test這個(gè)bit:如果鎖可用,則set這個(gè)bit,執(zhí)行臨界區(qū)代碼;否則代碼進(jìn)入重復(fù)測(cè)試這個(gè)bit的忙循環(huán),直到鎖可用。

自旋鎖的實(shí)現(xiàn):"test and set"的過(guò)程必須以原子方式完成;當(dāng)存在自旋鎖時(shí),申請(qǐng)自旋鎖的處理器進(jìn)入忙循環(huán),做不了任何有用的工作。

自旋鎖最初是為了在多處理器上使用設(shè)計(jì)的。工作在可搶占內(nèi)核的單處理器行為類(lèi)似于SMP。如果非搶占式的單處理器系統(tǒng)進(jìn)入自旋,會(huì)永遠(yuǎn)自旋下去;因此,非搶占式的單處理器上的自旋鎖被優(yōu)化為什么都不過(guò)(但改變IRQ掩碼狀態(tài)的例程是個(gè)例外)。

5.5.1 自旋鎖API介紹

<linux/spinlock.h> spinlock_t

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

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

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

5.5.2 自旋鎖和原子上下文

使用自旋鎖的核心規(guī)則:任何擁有自旋鎖的代碼都必須是原子的。它不能休眠,不能因?yàn)槿魏卧蚍艞壧幚砥鳎ǔ四承┲袛喾?wù))。

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

在擁有自旋鎖時(shí),需要禁止中斷。考慮一個(gè)例子:驅(qū)動(dòng)程序已經(jīng)獲得了一個(gè)控制對(duì)設(shè)備訪問(wèn)的自旋鎖;設(shè)備產(chǎn)生了一個(gè)中斷,中斷處理例程被調(diào)用,中斷處理例程在訪問(wèn)這個(gè)設(shè)備之前,也需要這個(gè)鎖;如果中斷處理例程在最初擁有鎖的代碼所在的處理器上自旋,非中斷代碼將沒(méi)有任何機(jī)會(huì)來(lái)釋放這個(gè)鎖,處理器將永遠(yuǎn)自旋下去。 //TODO Q:在一個(gè)CPU上被中斷的代碼,必須在同一CPU上恢復(fù)執(zhí)行?

擁有鎖的時(shí)間要盡可能短:避免其它CPU自旋等待相同鎖的時(shí)間過(guò)長(zhǎng);本CPU無(wú)法調(diào)度高優(yōu)先級(jí)進(jìn)程(搶占被禁止)。

5.5.3 自旋鎖函數(shù)

獲得自旋鎖的函數(shù)有4個(gè):
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會(huì)在獲取自旋鎖之前,禁止本地CPU上的中斷,并把先前中斷狀態(tài)保存在flags中。spin_lock_irp不跟蹤中斷標(biāo)志,用于明確知道沒(méi)有其它代碼禁止本地處理器的中斷時(shí)。spin_lock_bh只禁止軟件中斷,會(huì)讓硬件中斷繼續(xù)打開(kāi)。
如果某個(gè)自旋鎖可以被運(yùn)行在軟件中斷或硬件中斷上下文的代碼中獲得,則必須使用某個(gè)禁止中斷的spin_lock形式。

對(duì)應(yīng)有4個(gè)釋放自旋鎖的函數(shù):
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必須在同一函數(shù)中調(diào)用。

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

5.5.4 讀取者/寫(xiě)入者 自旋鎖

與rwsem類(lèi)似,允許多個(gè)reader同時(shí)進(jìn)入臨界區(qū),writer必須互斥訪問(wèn)。
可能造成reader饑餓,原因同rwsem。

下面是相關(guān)API。

靜/動(dòng)態(tài)初始化:

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 不明確的規(guī)則

在創(chuàng)建可被并行訪問(wèn)對(duì)象的同時(shí),定義控制訪問(wèn)的鎖。

如果某個(gè)已經(jīng)獲得鎖的函數(shù),試圖調(diào)用其他同樣試圖獲得同一鎖的函數(shù),代碼就會(huì)死鎖。

有些函數(shù)需要假定調(diào)用者已經(jīng)獲得了某些鎖:如果函數(shù)供外部調(diào)用,必須顯式的處理鎖定;如果是內(nèi)部函數(shù),必須顯式說(shuō)明這種假定,防止以后忘記。

5.6.2 鎖的順序規(guī)則

在必須獲得多個(gè)鎖時(shí),應(yīng)該始終以相同的順序獲得:假設(shè)線程1和線程2都要獲得LOCK1、LOCK2,如果線程1拿到了LOCK1,同時(shí)線程2拿到了LOCK2,就出現(xiàn)死鎖了。

兩條TIPS。如果需要獲得一個(gè)局部鎖(如設(shè)備鎖)和一個(gè)內(nèi)核更中心位置的鎖,先獲得局部鎖。如果需要同時(shí)擁有信號(hào)量和自旋鎖,必須先獲得信號(hào)量:在擁有自旋鎖時(shí)調(diào)用down(可能導(dǎo)致休眠)是個(gè)嚴(yán)重錯(cuò)誤。

5.6.3 細(xì)粒度鎖和粗粒度鎖的對(duì)比

粗粒度鎖影響性能,細(xì)粒度鎖增加復(fù)雜性。

應(yīng)該在最初使用粗粒度鎖,抑制自己過(guò)早考慮優(yōu)化的欲望,因?yàn)檎嬲男阅芷款i常出現(xiàn)在非預(yù)期的地方。
ch06 高級(jí)字符驅(qū)動(dòng)程序操作

6.1 ioctl

支持通過(guò)設(shè)備驅(qū)動(dòng)程序執(zhí)行各種類(lèi)型的硬件控制。

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

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

大多數(shù)ioctl實(shí)現(xiàn)都包括一個(gè)switch語(yǔ)句,根據(jù)cmd參數(shù)選擇對(duì)應(yīng)操作。

6.1.1 選擇ioctl命令

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

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

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

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

6.1.2 返回值

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

6.1.3 預(yù)定義命令

內(nèi)核預(yù)定義了一些ioctl命令。其中一類(lèi)是可用于任何文件的命令,幻數(shù)都是“T”。

6.1.4 使用ioctl參數(shù)

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

acess_ok函數(shù)驗(yàn)證地址:檢查所引用的內(nèi)存是否位于當(dāng)前進(jìn)程有訪問(wèn)權(quán)限的區(qū)域;特別地,要確保地址沒(méi)有指向內(nèi)核空間的內(nèi)存區(qū)。

6.1.5 權(quán)能與受限操作

<linux/capability.h>

基于權(quán)能(capability)的系統(tǒng)把特權(quán)操作分成獨(dú)立的組。

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

驅(qū)動(dòng)程序決定,是否可以對(duì)一個(gè)或多個(gè)打開(kāi)的文件,做非阻塞的讀取或?qū)懭搿?/div>

驅(qū)動(dòng)程序的poll方法:
unsigned int (*poll)(struct file *filp, poll_table *wait);
當(dāng)用戶(hù)程序在與驅(qū)動(dòng)相關(guān)聯(lián)的文件描述符上執(zhí)行poll/select時(shí)被調(diào)用。
分兩步處理:調(diào)用poll_wait向poll_table添加一個(gè)或多個(gè) 可指示poll狀態(tài)變化的等待隊(duì)列;返回一個(gè)描述操作是否可以利益執(zhí)行的位掩碼。

void poll_wait(struct file *, wait_queue_head_t *, poll_table);
ch12 pci驅(qū)動(dòng)

PCI是一套完整的規(guī)則,定義計(jì)算機(jī)的各個(gè)部分如何交互。PCI驅(qū)動(dòng)負(fù)責(zé)發(fā)現(xiàn)硬件并訪問(wèn)。

PCI體系結(jié)構(gòu)的目的:提高計(jì)算機(jī)和外設(shè)間數(shù)據(jù)傳輸性能;盡可能地和平臺(tái)無(wú)關(guān);簡(jiǎn)化外設(shè)的添加和移除。

PCI設(shè)備在啟動(dòng)時(shí)配置。設(shè)備驅(qū)動(dòng)必須可以訪問(wèn)配置信息,并完成設(shè)備初始化。不需要PCIqudo
12.1.1 PCI尋址

struct dev_pci 代表pci設(shè)備。

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

較新的工作站至少有兩根總線。每?jī)筛偩€用特殊外設(shè)bridges連接。PCI系統(tǒng)的總體布局是一棵樹(shù):總線通過(guò)bridges連接到上層總線上,直到樹(shù)根(0號(hào)總線)。

外設(shè)板答復(fù)3類(lèi)地址空間的查詢(xún):內(nèi)存單元,I/O端口,配置寄存器空間。同一PCI總線上的所有設(shè)備共享前兩個(gè)地址(也就是說(shuō),訪問(wèn)一個(gè)內(nèi)存單元時(shí),該總線上的所有設(shè)備都能看到總線周期)。配置空間利用了地理尋址,同一時(shí)刻只對(duì)應(yīng)一個(gè)slot,不會(huì)產(chǎn)生沖突。

下面的一堆沒(méi)有看懂。

12.1.2 啟動(dòng)時(shí)間 

當(dāng)PCI設(shè)備上電時(shí),它只響應(yīng)配置信息的存取:設(shè)備沒(méi)有內(nèi)存空間,也沒(méi)有I/O端口被映射到內(nèi)存空間;中斷報(bào)告也被關(guān)閉。

系統(tǒng)啟動(dòng)時(shí),固件(bios或內(nèi)核)讀取每個(gè)PCI外設(shè)的配置信息,為其分配地址去。在驅(qū)動(dòng)存取設(shè)備時(shí),它的內(nèi)存區(qū)和I/O區(qū)已經(jīng)被映射到處理器的地址空間。

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

12.1.3 配置寄存器和初始化

所有PCI設(shè)備都有至少256字節(jié)的配置地址空間:前64個(gè)字節(jié)是標(biāo)準(zhǔn)的,剩下的依賴(lài)于設(shè)備。

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

寄存器類(lèi)型:
vendorID    16位,硬件制造商
deviceID    16位,由硬件制造商選擇。vendorID和deviceID合起來(lái)被稱(chēng)作“簽名”,驅(qū)動(dòng)依靠簽名來(lái)標(biāo)識(shí)設(shè)備。
classID 16位,類(lèi)。一個(gè)驅(qū)動(dòng)可以同時(shí)支持幾個(gè)屬于同一類(lèi),但有不同簽名的設(shè)備。
subsystem venderID/subsystem deviceID 進(jìn)一步標(biāo)識(shí)設(shè)備。

struct pci_device_id 定義一個(gè)驅(qū)動(dòng)支持的PCI設(shè)備的列表:
__u32 vendor;    // 驅(qū)動(dòng)支持的設(shè)備的PCI供應(yīng)商和設(shè)備ID,PCI_ANY_ID表示支持任何。
__u32 device;
__u32 subvendor;
__u32 subdevice;
__u32 class;    // 驅(qū)動(dòng)支持的PCI設(shè)備種類(lèi),PCI_ANY_ID表示支持任何類(lèi)
__u32 class_mask;
kernel_ulong_t driver_data;    // 用來(lái)持有信息,也可以來(lái)標(biāo)識(shí)設(shè)備。

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

12.1.4 MODULEDEVICETABLE宏

MODULEDEVICETABLE將pci_device_id結(jié)構(gòu)輸出到用戶(hù)空間,供熱插拔和模塊加載系統(tǒng)知道驅(qū)動(dòng)模塊匹配的硬件。

該宏創(chuàng)建 指向struct pci_device_id的 局部變量_mod_pci_device_table;depmod遍歷所有模塊,將查找到的_mod_pci_device_table添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap中;當(dāng)有新的PCI設(shè)備連到計(jì)算機(jī)上,熱插拔系統(tǒng)使用文件modules.pcimap找到對(duì)應(yīng)的驅(qū)動(dòng)并加載。

12.1.5 注冊(cè)一個(gè)PCI驅(qū)動(dòng)

PCI驅(qū)動(dòng)結(jié)果,包含許多回調(diào)函數(shù)和變量。
struct pci_driver 
const char *name;    // 驅(qū)動(dòng)名稱(chēng),在內(nèi)核所有PCI驅(qū)動(dòng)里必須是唯一的。可以在/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核心發(fā)現(xiàn)與該驅(qū)動(dòng)匹配的struct pci_dev時(shí),調(diào)用probe。如果驅(qū)動(dòng)支持該pci_dev,正確初始化設(shè)備并返回0;否則返回一個(gè)錯(cuò)誤值。
int (*remove)(struct pci_dev);    // 當(dāng)struct pci_dev從系統(tǒng)中移除時(shí),由PCI核心調(diào)用;PCI驅(qū)動(dòng)被移除時(shí),也會(huì)調(diào)用remove函數(shù)來(lái)移除驅(qū)動(dòng)支持的設(shè)備。
int (*suspend)(struct pci_dev *dev, u32 state);    // 當(dāng)struct pci_dev被掛起時(shí),由PCI核心調(diào)用。state指明掛起狀態(tài)。可選的實(shí)現(xiàn)。
int (*resume)(struct pci_dev *dev);    // 當(dāng)pci_dev恢復(fù)時(shí),由PCI核心調(diào)用。

總之,創(chuàng)建一個(gè)pci驅(qū)動(dòng)時(shí),至少只需初始化4個(gè)成員:
static struct pci_driver pci_driver = {
    .name = "pci_skel",
    .id_table = ids,
    .probe = probe,
    .remove = remove,
};

注冊(cè)一個(gè)PCI驅(qū)動(dòng):
int pci_register_driver(struct pci_driver *drv);
.注冊(cè)struct pci_driver到PCI核心,成功返回0,失敗返回一個(gè)錯(cuò)誤值。它不返回綁定到驅(qū)動(dòng)上的設(shè)備號(hào);實(shí)際上,當(dāng)沒(méi)有設(shè)備被綁定到驅(qū)動(dòng)上時(shí),也算注冊(cè)成功:驅(qū)動(dòng)把自己注冊(cè)到PCI核心,當(dāng)PCI核心發(fā)現(xiàn)有新設(shè)備時(shí),PCI核心調(diào)用驅(qū)動(dòng)提供的回調(diào)函數(shù)初始化設(shè)備。

卸載一個(gè)PCI驅(qū)動(dòng):
void pci_unregister_driver(struct pci_driver *drv);
該函數(shù)移除綁定到這個(gè)驅(qū)動(dòng)上的所有設(shè)備,然后調(diào)用驅(qū)動(dòng)的remove函數(shù)。 TODO Q:調(diào)用remove 和 移除綁定到驅(qū)動(dòng)上的設(shè)備 不一樣么?

12.1.6 老式PCI探測(cè)

老版本內(nèi)核中,PCI驅(qū)動(dòng)手動(dòng)瀏覽系統(tǒng)中PCI設(shè)備。在2.6中已經(jīng)去掉了驅(qū)動(dòng)瀏覽PCI設(shè)備列表的功能(處于安全考慮),而是由PCI核心發(fā)現(xiàn)設(shè)備時(shí)調(diào)用已經(jīng)注冊(cè)的驅(qū)動(dòng)。

查找一個(gè)特定PCI設(shè)備:
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);
如果找到匹配設(shè)備,遞增struct pci_dev中的found引用計(jì)數(shù);當(dāng)驅(qū)動(dòng)用完該pci_dev時(shí),需要調(diào)用pci_dev_put遞減found引用計(jì)數(shù)。
from表示從哪個(gè)設(shè)備開(kāi)始探索,如果from設(shè)為NULL,表示返回第一個(gè)匹配的設(shè)備。
該函數(shù)不能從中斷上下文中調(diào)用。

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

12.1.7 使能pci設(shè)備

int pci_enable_device(struct pci_dev *dev);
在PCI驅(qū)動(dòng)的probe()函數(shù)中,必須調(diào)用pci_enable_device(),之后驅(qū)動(dòng)才可以存取PCI設(shè)備的資源。

12.1.8 存取配置空間

驅(qū)動(dòng)已經(jīng)探測(cè)到設(shè)備后,需要讀寫(xiě)3個(gè)地址空間:內(nèi)存、I/O端口和配置空間。其中,需要先讀取配置空間,獲取設(shè)備被映射到的內(nèi)存和I/O空間。

配置空間可以通過(guò)8/16/32為數(shù)據(jù)傳輸來(lái)讀寫(xiě):
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指從配置空間開(kāi)始的字節(jié)偏移,讀取到的值從val返回。
word和dword函數(shù)負(fù)責(zé)從小端到處理器本地字節(jié)序的轉(zhuǎn)換。

12.1.9 存取I/O和內(nèi)存空間

一個(gè)PCI設(shè)備至多有6個(gè)I/O地址區(qū):每個(gè)地址區(qū)要么是內(nèi)存區(qū),要么是I/O區(qū)。I/O寄存器不應(yīng)該被緩存,所以對(duì)于在內(nèi)存中實(shí)現(xiàn)I/O寄存器的設(shè)備,必須在配置寄存器中設(shè)置一個(gè)"內(nèi)存可預(yù)取"位,表明內(nèi)存區(qū)是否可預(yù)取。

一個(gè)PCI設(shè)備使用6個(gè)32位的配置寄存器,指明它的地址區(qū)大小和位置。這6個(gè)配置寄存器的符號(hào)名是PCI_ADDRESS_0到PCI_BASE_ADDRESS_5。

獲取設(shè)備的地址區(qū):
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個(gè)地址區(qū)的第一個(gè)地址,最后一個(gè)地址,flags資源標(biāo)識(shí)。
資源標(biāo)識(shí)定義在<linux/ioport.h>中。

12.1.10 PCI中斷

linux啟動(dòng)時(shí),固件給設(shè)備分配一個(gè)唯一的中斷號(hào)。
PCI設(shè)備的中斷號(hào)存儲(chǔ)在配置寄存器60(PCI_INTERRUPT_LINE),1字節(jié)寬。
如果不支持中斷,寄存器61(PCI_INTERRUPT_PIN)是0.
ch17 網(wǎng)絡(luò)驅(qū)動(dòng)程序

網(wǎng)絡(luò)驅(qū)動(dòng)程序異步接收來(lái)自外部世界的數(shù)據(jù)包,發(fā)送給內(nèi)核。

octet指一組8個(gè)的數(shù)據(jù)位,是能為網(wǎng)絡(luò)設(shè)備和協(xié)議所理解的最小單位。


17.1 snull設(shè)計(jì)

snull模擬了和遠(yuǎn)程主機(jī)的回話,不依賴(lài)于硬件。只支持ip流。

17.1.1 分配ip號(hào)

snull創(chuàng)建了兩個(gè)接口。通過(guò)一個(gè)接口傳輸?shù)臄?shù)據(jù),都會(huì)出現(xiàn)在另外一個(gè)接口上。

如果給兩個(gè)接口使用相同的ip號(hào),內(nèi)核會(huì)使用回環(huán)通道,而不會(huì)通過(guò)snull,所以在傳輸中需要修改源及目的地址。 // TODO


17.2 連接到內(nèi)核

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

17.2.1 設(shè)備注冊(cè)

網(wǎng)絡(luò)接口沒(méi)有主次設(shè)備號(hào)一類(lèi)的東西,而是對(duì)每個(gè)新檢測(cè)到的接口,向全局的網(wǎng)絡(luò)連接設(shè)備鏈表中插入一個(gè)數(shù)據(jù)結(jié)構(gòu) net_device。
<linux/netdevice.h>
struct net_device *snull_devs[2];
struct net_device包含了一個(gè)kobject和引用計(jì)數(shù)。

由于和其它結(jié)構(gòu)相關(guān),必須動(dòng)態(tài)分配:
struct net_device *alloc_netdev(int sizeof_priv, const char *name, void(*setup)(struct net_device *));
sizeof_priv是驅(qū)動(dòng)程序私有數(shù)據(jù)大小;name接口名稱(chēng),在用戶(hù)空間可見(jiàn);setup初始化函數(shù),用來(lái)設(shè)置net_device結(jié)構(gòu)剩余部分。
網(wǎng)絡(luò)子系統(tǒng)對(duì)alloc_netdev函數(shù),為不同種類(lèi)的接口封裝了許多函數(shù),最常用的是alloc_etherdev<linux/etherdevice.h>。

調(diào)用register_netdev注冊(cè)設(shè)備,立即生效。

17.2.2 初始化每個(gè)設(shè)備

在調(diào)用register_netdev之前,必須完成初始化。
先調(diào)用ether_setup(struct net_device)為結(jié)構(gòu)設(shè)置默認(rèn)值,然后再給我們關(guān)心的成員賦值。在scull中,flags設(shè)置為IFF_NOARP,表示接口不使用ARP協(xié)議;hard_header_cache設(shè)置為NULL,禁止對(duì)ARP的緩存。

struct net_device中priv成員保存設(shè)備私有數(shù)據(jù),和net_device結(jié)構(gòu)同時(shí)分配內(nèi)存(字符驅(qū)動(dòng)中fops->private是單獨(dú)分配的)。不推薦直接訪問(wèn)priv指針,一般通過(guò)netdev_priv函數(shù)訪問(wèn):
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從系統(tǒng)中刪除接口;free_netdev將net_device結(jié)構(gòu)返回給系統(tǒng)(引用計(jì)數(shù)減1);snull_teardown_pool是內(nèi)部清楚函數(shù),必須在free_netdev之前調(diào)用:調(diào)用free_netdev之后不能再引用設(shè)備或私有數(shù)據(jù)區(qū)。
17.3 net_device結(jié)構(gòu)細(xì)節(jié)

//一堆成員列表,米有看。。。
17.4 打開(kāi)和關(guān)閉

在接口能夠傳輸數(shù)據(jù)包之前,內(nèi)核必須打開(kāi)接口并賦予其地址。
用ifconfig給接口賦予地址時(shí),執(zhí)行兩個(gè)任務(wù):通過(guò)ioctl(SIOCSIFADDR)賦予地址,該任務(wù)由內(nèi)核進(jìn)行;通過(guò)ioctl(SIOCSIFFLAGS)設(shè)置dev->flags中的IFF_UP標(biāo)志以打開(kāi)接口,該命令會(huì)調(diào)用設(shè)備的open方法。

在接口被關(guān)閉時(shí),ifconfig使用ioctl(SIOCSIFFLAGS)清除IFF_UP標(biāo)志,然后調(diào)用stop函數(shù)。

open的工作:將MAC地址從硬件設(shè)備復(fù)制到dev->dev_addr;啟動(dòng)接口的傳輸隊(duì)列(允許接口傳輸數(shù)據(jù)包)。

stop的工作:關(guān)閉接口的傳輸隊(duì)列、

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

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

    /proc/devices/中的設(shè)備是通過(guò)insmod加載到內(nèi)核的,它的主設(shè)備號(hào)(major)作為mknod的參數(shù)。 
/dev/*.* 是通過(guò)mknod加上去的,格式:mknod device c/b major minor 注意要在/dev目錄下建立設(shè)備節(jié)點(diǎn),如:cd /dev 然后,mknod nrf24l01 c 238 0,用戶(hù)程序可通過(guò)設(shè)備名/dev/nrf24l01來(lái)調(diào)用驅(qū)動(dòng)程序從而訪問(wèn)硬件。 

--

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

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

xxx為設(shè)備名

posted on 2011-01-05 23:24 小默 閱讀(2978) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): Linux

導(dǎo)航

統(tǒng)計(jì)

  • 隨筆 - 289
  • 文章 - 0
  • 評(píng)論 - 84
  • 引用 - 0

留言簿(13)

隨筆分類(lèi)(287)

隨筆檔案(289)

漏洞

搜索

  •  

積分與排名

  • 積分 - 297095
  • 排名 - 89

最新評(píng)論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
              蜜月aⅴ免费一区二区三区| 国产欧美va欧美va香蕉在| 国产综合视频在线观看| 久久精品国产久精国产爱| 欧美大片免费观看| 中文精品在线| 精品91在线| 欧美日韩高清在线播放| 蜜臀91精品一区二区三区| 日韩一二在线观看| 噜噜噜噜噜久久久久久91| 亚洲欧美国产日韩天堂区| 欧美国产激情| 亚洲欧洲精品一区二区三区波多野1战4 | 亚洲第一精品夜夜躁人人爽| 亚洲欧美999| 欧美国产三级| 亚洲欧美日本在线| 亚洲欧美日韩国产成人| 亚洲尤物影院| 亚洲精品一区二区在线观看| 欧美精品成人| 亚洲国产黄色片| 中文欧美在线视频| 先锋资源久久| 国产午夜亚洲精品羞羞网站 | 亚洲天堂成人| 欧美中文在线观看| 香港成人在线视频| 亚洲欧洲在线观看| 国产亚洲精品综合一区91| 亚洲一级在线| 亚洲激情在线观看| 亚洲欧美日韩国产成人精品影院| 欧美一区二区三区四区夜夜大片| 99热在这里有精品免费| 91久久精品国产91久久性色tv| 欧美在线观看一区| 亚洲自拍偷拍麻豆| 卡一卡二国产精品| 亚洲看片网站| 国产精品一区亚洲| 欧美精品偷拍| 国产午夜精品在线观看| 亚洲裸体在线观看| 亚洲国产精品第一区二区| 亚洲国产精品成人| 亚洲国产精品久久久久秋霞影院| 欧美日韩国产在线看| 免费观看日韩| 久久久久久久性| 久久久www免费人成黑人精品| 久久综合一区| 亚洲精品偷拍| 亚洲福利国产精品| 欧美黄色网络| 中文成人激情娱乐网| 亚洲激情图片小说视频| 欧美日韩一区视频| 欧美久久99| 国产精品成人国产乱一区| 国产精品免费一区二区三区观看| 久久久久久69| 国产精品一区二区a| 国产视频一区二区在线观看 | 亚洲日本免费| 久久裸体视频| 亚洲久久视频| 亚洲欧洲精品一区| 91久久亚洲| 亚洲美女av黄| 一区二区三区国产精华| 亚洲淫性视频| 日韩一级黄色片| 美女成人午夜| 性做久久久久久久免费看| 欧美中文字幕| 亚洲美女毛片| 麻豆久久婷婷| 亚洲日本中文字幕区| 亚洲第一视频网站| 99热这里只有成人精品国产| 欧美国产综合| 一本大道久久a久久综合婷婷| 海角社区69精品视频| 欧美日韩国产一区精品一区| 91久久国产精品91久久性色| 日韩系列欧美系列| 欧美一区二区高清在线观看| 中国日韩欧美久久久久久久久| 亚洲高清成人| 日韩一区二区免费看| 欧美在线播放| 国产一区二区三区免费不卡| 猛男gaygay欧美视频| 一区二区欧美精品| 亚洲愉拍自拍另类高清精品| 亚洲精品之草原avav久久| 欧美精品久久久久久久免费观看 | 亚洲欧洲午夜| 欧美黑人在线观看| 欧美色精品天天在线观看视频| 久久av二区| 国产午夜精品在线| 亚洲成人在线视频播放| 久久疯狂做爰流白浆xx| 欧美日韩久久不卡| 国产日韩欧美精品在线| 99国产精品久久| 一本久道久久综合狠狠爱| 国产一级一区二区| 亚洲日本久久| 免费中文字幕日韩欧美| 夜夜嗨av一区二区三区免费区| 亚洲视频在线视频| 欧美区在线观看| 国产亚洲欧美日韩一区二区| 欧美黄色免费| 国产日产精品一区二区三区四区的观看方式 | 麻豆精品一区二区综合av | 夜夜狂射影院欧美极品| 先锋影音久久| 日韩午夜免费视频| 亚洲国产精品久久精品怡红院| 欧美精品亚洲一区二区在线播放| 久久久久久久97| 欧美一区免费视频| 亚洲影院免费| 99精品久久| 亚洲电影免费观看高清| 99伊人成综合| 欧美亚洲免费电影| 国产亚洲欧美激情| 欧美一区二区在线播放| 欧美大片网址| 一本色道久久综合亚洲精品小说 | 欧美一区国产二区| 欧美日韩一区二区免费视频| 亚洲作爱视频| 久久网站免费| 欧美三级午夜理伦三级中文幕| 亚洲欧洲日韩在线| 欧美在线播放| 欧美中文在线视频| 国产精品久久久久久久久免费樱桃| 亚洲一区二区三区精品在线观看 | 欧美日韩国产大片| 亚洲黄页视频免费观看| 午夜亚洲激情| 国内成人自拍视频| 久久久久久一区二区| 国产精品推荐精品| 久久人人爽人人爽| 韩国一区电影| 欧美大片一区二区| 欧美另类99xxxxx| 欧美暴力喷水在线| 欧美国产日韩亚洲一区| 欧美激情女人20p| 亚洲国产精品成人精品| 免费观看成人| 亚洲精品一区二区三区蜜桃久| 国产精品日韩一区二区| 久久免费的精品国产v∧| 久久久五月婷婷| 美玉足脚交一区二区三区图片| 欧美韩日一区二区三区| 亚洲免费在线观看视频| 国产精品国产三级国产aⅴ无密码 国产精品国产三级国产aⅴ入口 | 亚洲一区二区三区777| 亚洲女人天堂成人av在线| 精品福利免费观看| 久久久国产成人精品| 亚洲自拍偷拍视频| 国产精品一级二级三级| 欧美成人xxx| 91久久精品一区二区三区| 欧美午夜精品久久久久久孕妇| 亚洲国内自拍| 欧美黑人一区二区三区| 一区二区毛片| 亚洲一区综合| 91久久国产自产拍夜夜嗨 | 国产精品美女久久福利网站| 午夜久久久久久久久久一区二区| 亚洲国产成人久久| 亚洲一级一区| 一区二区三区 在线观看视频| 欧美伊人久久久久久久久影院| 美乳少妇欧美精品| 亚洲午夜精品| 精品va天堂亚洲国产| 亚洲欧美日韩天堂一区二区| 亚洲人成在线观看一区二区| 欧美日精品一区视频| 久久久噜噜噜久久| 久久久97精品| 国产精品成人在线观看| 久久久久看片| 亚洲欧美国产高清|