1. 程式人生 > >linux裝置驅動模型一字元裝置open系統呼叫流程

linux裝置驅動模型一字元裝置open系統呼叫流程

從前面 的例子可以看到,我們在操作一個調和時都 是通過open系統呼叫先去開啟這個裝置,不管是裝置還是檔案,我們要訪問它都要稱通過open函式來先開啟, 這樣才能呼叫其它的函式如read、write來操作它,即通知核心新建一個代表該檔案的結構,並且返回該檔案的描述符(一個整數),該描述符在程序內唯一。

在linux系統程序中,分為核心空間和使用者空間,當一個任務(程序)執行系統呼叫而陷入核心程式碼中執行時,我們就稱程序處於核心執行態(核心態)。在核心態下,CPU可執行任何指令。當程序在執行使用者自己的程式碼時,則稱其處於使用者執行態(使用者態)。使用者態不能訪問核心空間,包括程式碼和資料。所有程序的核心空間(3G-4G)都是共享的。當我們在使用者空間呼叫open之後,會產生一個軟中斷,然後通過系統呼叫進入核心空間。通過系統呼叫號,我們就可以跳轉到該中斷例程的入口地址。

這裡分析一下open的呼叫流程.

open()的格式如下:

int open(const char * pathname,int oflag, mode_t mode )
pathname:代表需要開啟的檔案的檔名;
oflag:表示開啟的標識,具體的核心支援如下標記位(include\asm-generic\fcntl.h):

#define O_ACCMODE	00000003
#define O_RDONLY	00000000  //只讀開啟
#define O_WRONLY	00000001  //只寫開啟
#define O_RDWR		00000002  //讀寫開啟
#ifndef O_CREAT
#define O_CREAT		00000100	//檔案不存在則建立,需要mode_t
#endif
#ifndef O_EXCL
#define O_EXCL		00000200	//如果同時指定了O_CREAT,而檔案已經存在,則出錯 
#endif
#ifndef O_NOCTTY
#define O_NOCTTY	00000400	//如果pathname代表終端裝置,則不將此裝置分配作為此程序的控制終端
#endif
#ifndef O_TRUNC
#define O_TRUNC		00001000	//如果此檔案存在,而且為只讀或只寫成功開啟,則將其長度截短為0 
#endif
#ifndef O_APPEND
#define O_APPEND	00002000    //每次寫時都加到檔案的尾端
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK	00004000     //如果pathname指的是一個FIFO、一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I / O操作設定非阻塞
#endif
#ifndef O_SYNC
#define O_SYNC		00010000 	//使每次write都等到物理I/O操作完成
#endif
#ifndef FASYNC
#define FASYNC		00020000	//相容BSD的fcntl同步操作
#endif
#ifndef O_DIRECT
#define O_DIRECT	00040000	//直接磁碟操作標識,每次讀寫都不使用核心提供的快取,直接讀寫磁碟裝置
#endif
#ifndef O_LARGEFILE
#define O_LARGEFILE	00100000	// 大檔案標識
#endif
#ifndef O_DIRECTORY
#define O_DIRECTORY	00200000	//必須是目錄
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW	00400000	//不獲取連線檔案
#endif
#ifndef O_NOATIME
#define O_NOATIME	01000000
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC	02000000	/* set close_on_exec */
#endif
#ifndef O_NDELAY
#define O_NDELAY	O_NONBLOCK
#endif

當新建立一個檔案時,需要指定mode引數,以下說明的格式如巨集定義名稱<實際常數值>:
 描述如下(include\linux\stat.h):

#define S_IRWXU 00700	//檔案擁有者有讀寫執行許可權
#define S_IRUSR 00400	//檔案擁有者僅有讀許可權	
#define S_IWUSR 00200	//檔案擁有者僅有寫許可權
#define S_IXUSR 00100	//檔案擁有者僅有執行許可權

#define S_IRWXG 00070	//組使用者有讀寫執行許可權
#define S_IRGRP 00040	//組使用者僅有讀許可權
#define S_IWGRP 00020	//組使用者僅有寫許可權
#define S_IXGRP 00010	//組使用者僅有執行許可權

#define S_IRWXO 00007	//其他使用者有讀寫執行許可權
#define S_IROTH 00004	//其他使用者僅有讀許可權
#define S_IWOTH 00002	//其他使用者僅有寫許可權
#define S_IXOTH 00001	//其他使用者僅有執行許可權

系統呼叫號定義在arch/x86/include/asm/unistd_32.h中:

#define __NR_restart_syscall      0
#define __NR_exit		  1
#define __NR_fork		  2
#define __NR_read		  3
#define __NR_write		  4
#define __NR_open		  5
#define __NR_close		  6
#define __NR_waitpid		  7
#define __NR_creat		  8
#define __NR_link		  9
#define __NR_unlink		 10
#define __NR_execve		 11
#define __NR_chdir		 12
#define __NR_time		 13
#define __NR_mknod		 14
#define __NR_chmod		 15

當open系統呼叫產生時,就會進入下面這個函式():

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
	long ret;

	/*檢查是否應該不考慮使用者層傳遞的標誌、總是強行設定 
    O_LARGEFILE標誌。如果底層處理器的字長不是32位,就是這種 
    情況*/ 
	if (force_o_largefile())
		flags |= O_LARGEFILE;
	 /*實際工作*/ 
	ret = do_sys_open(AT_FDCWD, filename, flags, mode);
	/* avoid REGPARM breakage on x86: */
	asmlinkage_protect(3, ret, filename, flags, mode);
	return ret;
}

我們看下SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) 展開是怎麼樣的

首先看下巨集SYSCALL_DEFINE3

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
再看下SYSCALL_DEFINEx
#define SYSCALL_DEFINEx(x, sname, ...)				\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
再看下__SYSCALL_DEFINEx   
#define __SYSCALL_DEFINEx(x, name, ...)					\
	asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
這裡對對應__SC_DECL3
#define __SC_DECL1(t1, a1)	t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
這們一步步展開SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)代替進去,可以得到 
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
 = SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 
=asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))
=asmlinkage long sys_open(const char __user* filename, int flags, int mode)
這個才是真正的函式原型

在sys_open裡面繼續呼叫do_sys_open完成 open操作

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
	 /*從程序地址空間讀取該檔案的路徑名*/  
	char *tmp = getname(filename);
	int fd = PTR_ERR(tmp);

	if (!IS_ERR(tmp)) {
		 /*在核心中,每個開啟的檔案由一個檔案描述符表示 
        該描述符在特定於程序的陣列中充當位置索引(陣列是 
        task_struct->files->fd_arry),該陣列的元素包含了file結構,其中 
        包括每個開啟檔案的所有必要資訊。因此,呼叫下面 
        函式查詢一個未使用的檔案描述符,返回的是上面 
        說的陣列的下標*/  
		fd = get_unused_fd_flags(flags);
		if (fd >= 0) {
			/*fd獲取成功則開始開啟檔案,此函式是主要完成開啟功能的函式*/
			//如果分配fd成功,則建立一個file物件
			struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
			if (IS_ERR(f)) {
				put_unused_fd(fd);
				fd = PTR_ERR(f);
			} else {
				/*檔案如果開啟成功,呼叫fsnotify_open()函式,根據inode所指定的資訊進行開啟
			函式(引數為f)將該檔案加入到檔案監控的系統中。該系統是用來監控檔案被開啟,建立,
			讀寫,關閉,修改等操作的*/ 
				fsnotify_open(f->f_path.dentry);
				/*將檔案指標安裝在fd陣列中
			將struct file *f加入到fd索引位置處的陣列中。如果後續過程中,有對該檔案描述符的
			操作的話,就會通過查詢該陣列得到對應的檔案結構,而後在進行相關操作。*/
				fd_install(fd, f);
			}
		}
		putname(tmp);
	}
	return fd;
}

該函式主要分為如下幾個步驟來完成開啟檔案的操作:
1.將檔名引數從使用者態拷貝至核心,呼叫函式get_name();
2.從程序的檔案表中找到一個空閒的檔案表指標,呼叫了函式get_unused_fd_flgas();
3.完成真正的開啟操作,呼叫函式do_filp_open();
4.將開啟的檔案新增到程序的檔案表陣列中,呼叫函式fd_install();

getname函式主要的任務是將檔名filename從使用者態拷貝至核心態

char * getname(const char __user * filename)
{
	char *tmp, *result;

	result = ERR_PTR(-ENOMEM);
	tmp = __getname(); //從核心快取中分配空間;  
	if (tmp)  {
		//將檔名從使用者態拷貝至核心態; 
		int retval = do_getname(filename, tmp);

		result = tmp;
		if (retval < 0) {//如果拷貝失敗,則呼叫__putname()釋放__getname()中申請的空間; 
			__putname(tmp);
			result = ERR_PTR(retval);
		}
	}
	audit_getname(result);
	return result;
}
get_unused_fd_flags實際呼叫的是alloc_fd
#define get_unused_fd_flags(flags) alloc_fd(0, (flags))
/*
 * allocate a file descriptor, mark it busy.
 */
int alloc_fd(unsigned start, unsigned flags)
{
	struct files_struct *files = current->files;//獲得當前程序的files_struct 結構
	unsigned int fd;
	int error;
	struct fdtable *fdt;

	spin_lock(&files->file_lock);
repeat:
	fdt = files_fdtable(files);
	fd = start;
	if (fd < files->next_fd) //從上一次開啟的fd的下一個fd開始搜尋空閒的fd  
		fd = files->next_fd;

	if (fd < fdt->max_fds)//尋找空閒的fd,返回值為空閒的fd  
		fd = find_next_zero_bit(fdt->open_fds->fds_bits,
					   fdt->max_fds, fd);
	//如果有必要,即開啟的fd超過max_fds,則需要expand當前程序的fd表;  
    //返回值error<0表示出錯,error=0表示無需expand,error=1表示進行了expand;
	error = expand_files(files, fd);
	if (error < 0)
		goto out;

	/*
	 * If we needed to expand the fs array we
	 * might have blocked - try again.
	 */
	 //error=1表示進行了expand,那麼此時需要重新去查詢空閒的fd;  
	if (error)
		goto repeat;

	//設定下一次查詢的起始fd,即本次找到的空閒的fd的下一個fd,記錄在files->next_fd中;  
	if (start <= files->next_fd)
		files->next_fd = fd + 1;

	FD_SET(fd, fdt->open_fds);
	if (flags & O_CLOEXEC)
		FD_SET(fd, fdt->close_on_exec);
	else
		FD_CLR(fd, fdt->close_on_exec);
	error = fd;
#if 1
	/* Sanity check */
	if (rcu_dereference(fdt->fd[fd]) != NULL) {
		printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
		rcu_assign_pointer(fdt->fd[fd], NULL);
	}
#endif

out:
	spin_unlock(&files->file_lock);
	return error;
}
該函式為需要開啟的檔案在當前程序內分配一個空閒的檔案描述符fd,該fd就是open()系統呼叫的返回值

do_filp_open函式的一個重要作用就是根據傳遞近來的許可權進行分析,並且分析傳遞近來的路徑名字,根據路徑名逐個解析成dentry,並且通過dentry找到inode,inode就是記錄著該檔案相關的資訊, 包括檔案的建立時間和檔案屬性所有者等等資訊,根據這些資訊就可以找到對應的檔案操作方法。在這個過程當中有一個臨時的結構體用於儲存在查詢過程中的相關資訊,就是

struct nameidata {
	struct path	path;//當前目錄的dentry資料結構
	struct qstr	last;//這個結構體也是臨時性的,主要用來儲存當前目錄的名稱,雜湊值。
	unsigned int	flags;
	int		last_type;
	unsigned	depth;//連線檔案的深度(可能一個連線檔案跟到最後還是一個了連線檔案)
	//用來儲存連線檔案的一些資訊,下標表示連線檔案的深度
	char *saved_names[MAX_NESTED_LINKS + 1];

	/* Intent data */
	union {
		struct open_intent open;
	} intent;
};


struct file *do_filp_open(int dfd, const char *pathname,
		int open_flag, int mode, int acc_mode)
{
	struct file *filp;
	struct nameidata nd;
	int error;
	struct path path;
	int count = 0;
	int flag = open_to_namei_flags(open_flag); /*改變引數flag的值,具體做法是flag+1*/ 
	int force_reval = 0;

	if (!(open_flag & O_CREAT))
		mode = 0;

	/*
	 * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only
	 * check for O_DSYNC if the need any syncing at all we enforce it's
	 * always set instead of having to deal with possibly weird behaviour
	 * for malicious applications setting only __O_SYNC.
	 */
	if (open_flag & __O_SYNC)/*根據__O_SYNC標誌來設定O_DSYNC 標誌,用以防止惡意破壞程式*/ 
		open_flag |= O_DSYNC;

	if (!acc_mode)/*設定訪問許可權*/ 
		acc_mode = MAY_OPEN | ACC_MODE(open_flag);

	/* O_TRUNC implies we need access checks for write permissions */
	if (open_flag & O_TRUNC)/*根據 O_TRUNC標誌設定寫許可權 */ 
		acc_mode |= MAY_WRITE;

	/* Allow the LSM permission hook to distinguish append 
	   access from general write access. */
	if (open_flag & O_APPEND)/* 設定O_APPEND 標誌*/  
		acc_mode |= MAY_APPEND;

	/* find the parent */
reval:
	error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);//初始化nd
	if (error)
		return ERR_PTR(error);
	if (force_reval)
		nd.flags |= LOOKUP_REVAL;

	current->total_link_count = 0;
	error = link_path_walk(pathname, &nd);//路徑名解析函式,將一個路徑名最終轉化為一個dentry
	if (error) {
		filp = ERR_PTR(error);
		goto out;
	}
	if (unlikely(!audit_dummy_context()) && (open_flag & O_CREAT))
		audit_inode(pathname, nd.path.dentry);

	/*
	 * We have the parent and last component.
	 */

	error = -ENFILE;
	filp = get_empty_filp();// 從程序檔案表中獲取一個未使用的檔案結構指標,空則出錯返回
	if (filp == NULL)
		goto exit_parent;
	nd.intent.open.file = filp;
	filp->f_flags = open_flag;
	nd.intent.open.flags = flag;
	nd.intent.open.create_mode = mode;
	nd.flags &= ~LOOKUP_PARENT;
	nd.flags |= LOOKUP_OPEN;
	if (open_flag & O_CREAT) {
		nd.flags |= LOOKUP_CREATE;
		if (open_flag & O_EXCL)
			nd.flags |= LOOKUP_EXCL;
	}
	if (open_flag & O_DIRECTORY)
		nd.flags |= LOOKUP_DIRECTORY;
	if (!(open_flag & O_NOFOLLOW))
		nd.flags |= LOOKUP_FOLLOW;
	filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);//返回一個file結構
	while (unlikely(!filp)) { /* trailing symlink *///符號連結
		struct path holder;
		struct inode *inode = path.dentry->d_inode;
		void *cookie;
		error = -ELOOP;
		/* S_ISDIR part is a temporary automount kludge */
		if (!(nd.flags & LOOKUP_FOLLOW) && !S_ISDIR(inode->i_mode))
			goto exit_dput;
		if (count++ == 32)
			goto exit_dput;
		/*
		 * This is subtle. Instead of calling do_follow_link() we do
		 * the thing by hands. The reason is that this way we have zero
		 * link_count and path_walk() (called from ->follow_link)
		 * honoring LOOKUP_PARENT.  After that we have the parent and
		 * last component, i.e. we are in the same situation as after
		 * the first path_walk().  Well, almost - if the last component
		 * is normal we get its copy stored in nd->last.name and we will
		 * have to putname() it when we are done. Procfs-like symlinks
		 * just set LAST_BIND.
		 */
		nd.flags |= LOOKUP_PARENT;
		error = security_inode_follow_link(path.dentry, &nd);
		if (error)
			goto exit_dput;
		error = __do_follow_link(&path, &nd, &cookie);//查詢符號連結對應的目錄中的最後一項
		if (unlikely(error)) {
			/* nd.path had been dropped */
			if (!IS_ERR(cookie) && inode->i_op->put_link)
				inode->i_op->put_link(path.dentry, &nd, cookie);
			path_put(&path);
			release_open_intent(&nd);
			filp = ERR_PTR(error);
			goto out;
		}
		holder = path;
		nd.flags &= ~LOOKUP_PARENT;
		filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
		if (inode->i_op->put_link)
			inode->i_op->put_link(holder.dentry, &nd, cookie);
		path_put(&holder);
	}
out:
	if (nd.root.mnt)
		path_put(&nd.root);
	if (filp == ERR_PTR(-ESTALE) && !force_reval) {
		force_reval = 1;
		goto reval;
	}
	return filp;//成功,返回

exit_dput:
	path_put_conditional(&path, &nd);
	if (!IS_ERR(nd.intent.open.file))
		release_open_intent(&nd);
exit_parent:
	path_put(&nd.path);
	filp = ERR_PTR(error);
	goto out;
}

當核心要訪問一個檔案的時候,第一步要做的是找到這個檔案,而查詢檔案的過程在vfs裡面是由link_path_walk函式來完成的,在path_init的時候我們可以看到傳進去的引數有一個LOOKUP_PARENT,它的含義是查詢最後一個分量名所在的目錄。也就是當這個函式返回的時候,我們得到了一個路徑名中最後一個分量所在的目錄。

接著呼叫do_last返回最後一個分量對應的file指標,我們關注一下這個函式

static struct file *do_last(struct nameidata *nd, struct path *path,
			    int open_flag, int acc_mode,
			    int mode, const char *pathname)
{
	struct dentry *dir = nd->path.dentry;
	struct file *filp;
	int error = -EISDIR;

	switch (nd->last_type) {// 檢查最後一段檔案或目錄名的屬性情況
	case LAST_DOTDOT:
		follow_dotdot(nd);
		dir = nd->path.dentry;
	case LAST_DOT:
		if (nd->path.mnt->mnt_sb->s_type->fs_flags & FS_REVAL_DOT) {
			if (!dir->d_op->d_revalidate(dir, nd)) {
				error = -ESTALE;
				goto exit;
			}
		}
		/* fallthrough */
	case LAST_ROOT:
		if (open_flag & O_CREAT)
			goto exit;
		/* fallthrough */
	case LAST_BIND:
		audit_inode(pathname, dir);
		goto ok;
	}

	/* trailing slashes? */
	if (nd->last.name[nd->last.len]) {
		if (open_flag & O_CREAT)
			goto exit;
		nd->flags |= LOOKUP_DIRECTORY | LOOKUP_FOLLOW;
	}

	/* just plain open? */
	if (!(open_flag & O_CREAT)) {//沒有建立標誌,即檔案存在
		error = do_lookup(nd, &nd->last, path);//找到路徑中最後一項對應的目錄項
		if (error)
			goto exit;
		error = -ENOENT;
		if (!path->dentry->d_inode)
			goto exit_dput;
		if (path->dentry->d_inode->i_op->follow_link)
			return NULL;
		error = -ENOTDIR;
		if (nd->flags & LOOKUP_DIRECTORY) {
			if (!path->dentry->d_inode->i_op->lookup)
				goto exit_dput;
		}
		path_to_nameidata(path, nd);//賦值到nd結構
		audit_inode(pathname, nd->path.dentry);
		goto ok;
	}

	/* OK, it's O_CREAT */
	//檔案不存在,需要建立
	mutex_lock(&dir->d_inode->i_mutex);

	path->dentry = lookup_hash(nd);//獲取最後路徑名中最後一項對應的目錄項
	path->mnt = nd->path.mnt;

	error = PTR_ERR(path->dentry);
	if (IS_ERR(path->dentry)) {
		mutex_unlock(&dir->d_inode->i_mutex);
		goto exit;
	}

	if (IS_ERR(nd->intent.open.file)) {
		error = PTR_ERR(nd->intent.open.file);
		goto exit_mutex_unlock;
	}

	/* Negative dentry, just create the file */
	if (!path->dentry->d_inode) {//沒有索引節點與目錄項關聯
		/*
		 * This write is needed to ensure that a
		 * ro->rw transition does not occur between
		 * the time when the file is created and when
		 * a permanent write count is taken through
		 * the 'struct file' in nameidata_to_filp().
		 */
		error = mnt_want_write(nd->path.mnt);
		if (error)
			goto exit_mutex_unlock;
		error = __open_namei_create(nd, path, open_flag, mode);//建立相應的索引節點
		if (error) {
			mnt_drop_write(nd->path.mnt);
			goto exit;
		}
		filp = nameidata_to_filp(nd);/*根據nameidata 得到相應的file結構*/
		mnt_drop_write(nd->path.mnt);
		if (!IS_ERR(filp)) {
			error = ima_file_check(filp, acc_mode);
			if (error) {
				fput(filp);
				filp = ERR_PTR(error);
			}
		}
		return filp;
	}

	/*
	 * It already exists.
	 */
	mutex_unlock(&dir->d_inode->i_mutex);
	audit_inode(pathname, path->dentry);

	error = -EEXIST;
	if (open_flag & O_EXCL)
		goto exit_dput;

	if (__follow_mount(path)) {
		error = -ELOOP;
		if (open_flag & O_NOFOLLOW)
			goto exit_dput;
	}

	error = -ENOENT;
	if (!path->dentry->d_inode)
		goto exit_dput;

	if (path->dentry->d_inode->i_op->follow_link)
		return NULL;

	path_to_nameidata(path, nd);
	error = -EISDIR;
	if (S_ISDIR(path->dentry->d_inode->i_mode))
		goto exit;
ok:
	filp = finish_open(nd, open_flag, acc_mode);//完成檔案開啟操作
	return filp;

exit_mutex_unlock:
	mutex_unlock(&dir->d_inode->i_mutex);
exit_dput:
	path_put_conditional(path, nd);
exit:
	if (!IS_ERR(nd->intent.open.file))
		release_open_intent(nd);
	path_put(&nd->path);
	return ERR_PTR(error);
}

首先進行一些判斷,然後看是否需要建立檔案,如果需要建立的,則建立檔案。如果檔案存在的話,直接呼叫finish_open完成檔案開啟,我們這裡關注下開啟檔案的
static struct file *finish_open(struct nameidata *nd,
				int open_flag, int acc_mode)
{
	struct file *filp;
	int will_truncate;
	int error;

	/*檢測是否截斷檔案標誌*/
	will_truncate = open_will_truncate(open_flag, nd->path.dentry->d_inode);
	if (will_truncate) {/*要截斷的話就要獲取寫許可權*/
		error = mnt_want_write(nd->path.mnt);
		if (error)
			goto exit;
	}
	 //may_open執行許可權檢測、檔案開啟和truncate的操作
	error = may_open(&nd->path, acc_mode, open_flag);
	if (error) {
		if (will_truncate)
			mnt_drop_write(nd->path.mnt);
		goto exit;
	}
	filp = nameidata_to_filp(nd);   /*根據nameidata 得到相應的file結構*/
	if (!IS_ERR(filp)) {
		error = ima_file_check(filp, acc_mode);
		if (error) {
			fput(filp);
			filp = ERR_PTR(error);
		}
	}
	if (!IS_ERR(filp)) {
		if (will_truncate) {// //處理截斷
			error = handle_truncate(&nd->path);
			if (error) {
				fput(filp);
				filp = ERR_PTR(error);
			}
		}
	}
	/*
	 * It is now safe to drop the mnt write
	 * because the filp has had a write taken
	 * on its behalf.
	 */
	if (will_truncate)  //安全的放棄寫許可權
		mnt_drop_write(nd->path.mnt);
	return filp;

exit:
	if (!IS_ERR(nd->intent.open.file))
		release_open_intent(nd);
	path_put(&nd->path);
	return ERR_PTR(error);
}

這裡主要呼叫nameidata_to_filp得到相應的file結構

struct file *nameidata_to_filp(struct nameidata *nd)
{
	const struct cred *cred = current_cred();
	struct file *filp;

	/* Pick up the filp from the open intent */
	filp = nd->intent.open.file;/// 把相關 file結構的指標賦予 filp
	/* Has the filesystem initialised the file for us? */
	if (filp->f_path.dentry == NULL)
		filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp,
				     NULL, cred);
	else
		path_put(&nd->path);
	return filp;
}

呼叫__dentry_open
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
					struct file *f,
					int (*open)(struct inode *, struct file *),
					const struct cred *cred)
{
	struct inode *inode;
	int error;

	f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |//初始化f_mode
				FMODE_PREAD | FMODE_PWRITE;
	inode = dentry->d_inode;
	if (f->f_mode & FMODE_WRITE) {
		error = __get_file_write_access(inode, mnt);
		if (error)
			goto cleanup_file;
		if (!special_file(inode->i_mode))
			file_take_write(f);
	}

	f->f_mapping = inode->i_mapping;
	f->f_path.dentry = dentry;//初始化目錄項物件
	f->f_path.mnt = mnt;//初始化檔案系統物件
	f->f_pos = 0;
	f->f_op = fops_get(inode->i_fop);//為檔案操作建立起所有方法
	file_move(f, &inode->i_sb->s_files);//把檔案物件插入到檔案系統超級塊的s_files欄位所指向的開啟檔案的連結串列。

	error = security_dentry_open(f, cred);
	if (error)
		goto cleanup_all;

	if (!open && f->f_op)//傳進來的open為NULL
		open = f->f_op->open;
	if (open) {
		error = open(inode, f);
		if (error)
			goto cleanup_all;
	}
	ima_counts_get(f);

	f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);//初始化f_f_flags

	file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);//初始化預讀的資料結構

	/* NB: we're sure to have correct a_ops only after f_op->open */
	if (f->f_flags & O_DIRECT) {//檢查直接IO操作是否可以作用於檔案
		if (!f->f_mapping->a_ops ||
		    ((!f->f_mapping->a_ops->direct_IO) &&
		    (!f->f_mapping->a_ops->get_xip_mem))) {
			fput(f);
			f = ERR_PTR(-EINVAL);
		}
	}

	return f;

cleanup_all:
	fops_put(f->f_op);
	if (f->f_mode & FMODE_WRITE) {
		put_write_access(inode);
		if (!special_file(inode->i_mode)) {
			/*
			 * We don't consider this a real
			 * mnt_want/drop_write() pair
			 * because it all happenend right
			 * here, so just reset the state.
			 */
			file_reset_write(f);
			mnt_drop_write(mnt);
		}
	}
	file_kill(f);
	f->f_path.dentry = NULL;
	f->f_path.mnt = NULL;
cleanup_file:
	put_filp(f);
	dput(dentry);
	mntput(mnt);
	return ERR_PTR(error);
}
這裡主要是進行一些賦值操作

對應於這裡,傳進來的open指標為NULL,如果相應file_operations結構存在的話就呼叫它的open函式

對於每個檔案在建立 的時候會賦值對其進行操作的file_operations結構,這個結構對於一類檔案是一樣的,例如 對應於字元裝置是chrdev_open

const struct file_operations def_chr_fops = {
	.open = chrdev_open,
};

但開啟之後,我們可以重新獲取它們的file_operations結構,這個是在註冊裝置驅動的時候為該類裝置賦予的,也就是我們在驅動裡面實現的,而前面的預設file_operations就是為了完成這個轉換的,def_chr_fops只起過渡作用,它的open方法要去尋找硬體驅動的支撐。
static int chrdev_open(struct inode *inode, struct file *filp)
{
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	spin_lock(&cdev_lock);
	p = inode->i_cdev;
	if (!p) { /* 很顯然,第一次開啟的時候是NULL */
		struct kobject *kobj;
		int idx;
		spin_unlock(&cdev_lock);
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);/* 找到和裝置號i_rdev對應的kobj,其實就是cdev了,因為cdev中包含kobj;idx儲存的是次裝置號,後面會分析kobj_lookup()函式 */
		if (!kobj)
			return -ENXIO;
		new = container_of(kobj, struct cdev, kobj);//得到cdev
		spin_lock(&cdev_lock);
		/* Check i_cdev again in case somebody beat us to it while
		   we dropped the lock. */
		p = inode->i_cdev;
		if (!p) {
			inode->i_cdev = p = new;/* 把找到的cdev儲存到inode的icdev中 */
			list_add(&inode->i_devices, &p->list); /* inode加入到cdev的連結串列中 */
			new = NULL;
		} else if (!cdev_get(p))
			ret = -ENXIO;
	} else if (!cdev_get(p))
		ret = -ENXIO;
	spin_unlock(&cdev_lock);
	cdev_put(new);
	if (ret)
		return ret;

	ret = -ENXIO;
	/* 
	儲存使用者的fops,以後你再呼叫read, write, ioctl系統呼叫的時候就直接使用了,你懂的 
	*/
	filp->f_op = fops_get(p->ops);
	if (!filp->f_op) // 如果你沒有註冊fops
		goto out_cdev_put;

	if (filp->f_op->open) {//判斷open函式是否存在
		ret = filp->f_op->open(inode,filp);//* 呼叫使用者的open函式,我們前面寫的驅動
		if (ret)
			goto out_cdev_put;
	}

	return 0;

 out_cdev_put:
	cdev_put(p);
	return ret;
}
在這個函式裡,我們重新 對f_op賦值了,這裡的f_op就是我們在寫驅動時寫的系統呼叫函數了。後面還呼叫了open方法

這裡呼叫 kobj_lookup找到前面我們在註冊驅動新增裝置時新增的相應的kobj

struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
	struct kobject *kobj;
	struct probe *p;
	unsigned long best = ~0UL;

retry:
	mutex_lock(domain->lock);
	 /* 根據主裝置號和裝置號查詢它的一畝三分地。因為要支援2^12次方也就是4096個主裝置號,
	 但只使用了前255個主裝置號索引,所以這255個索引對應的probe結構都有一個單向
	 連結串列儲存著大於255的主裝置號(被255整除後的索引相等)  */
	for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
		struct kobject *(*probe)(dev_t, int *, void *);
		struct module *owner;
		void *data;
		// 比較,看是否真找到了,因為有連結串列存在
		if (p->dev > dev || p->dev + p->range - 1 < dev)
			continue;
		if (p->range - 1 >= best)
			break;
		if (!try_module_get(p->owner))
			continue;
		owner = p->owner;
		data = p->data;//data就是cdev
		probe = p->get;
		best = p->range - 1;
		*index = dev - p->dev;//得到次裝置號
		  /* 呼叫的lock就是exact_lock()函式,增加對該字元裝置驅動的引用,防止被解除安裝什麼的 */
		if (p->lock && p->lock(dev, data) < 0) {
			module_put(owner);
			continue;
		}
		mutex_unlock(domain->lock);
		 /*呼叫的probe就是exact_match()函式,獲取cdev的kobj指標 */
		kobj = probe(dev, index, data);
		/* Currently ->owner protects _only_ ->probe() itself. */
		module_put(owner);
		if (kobj)
			return kobj;
		goto retry;
	}
	mutex_unlock(domain->lock);
	return NULL;
}

到這裡do_filp_open的流程就基本完成了,即返回了一個file結構
void fd_install(unsigned int fd, struct file *file)
{
	struct files_struct *files = current->files;
	struct fdtable *fdt;
	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);//獲取fdtbale
	BUG_ON(fdt->fd[fd] != NULL);
	rcu_assign_pointer(fdt->fd[fd], file);//fd和file關係到fdtbale
	spin_unlock(&files->file_lock);
}

這樣,我們的open系統呼叫就基本上完成了,我們得到一個fd,這個fd關聯著一個file結構,這樣以後,我們就可以通過這個fd結構操作相應的檔案了

最後還是一樣看下整個簡略的流程圖