【Linux内核编程】 设备管理与文件IO操作

一:Linux中的设备管理

Linux采用文件系统管理硬件设备,所有的设备都看成是特殊的文件,从而将硬件设备的特性及管理细节对用户隐藏起来,实现设备无关性。

二:设备管理的特点

每个设备都对应文件系统中的一个索引节点,都有一个文件名。

应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。

对设备的使用类似于对文件的存取。

设备驱动程序都是系统内核的一部分,它们必须为系统内核或者它们的子系统提供一个标准的接口。

设备驱动程序使用一些标准的内核服务,如内存分配等。

三:设备分类

1. 按设备属主关系

系统设备

用户设备

2. 按设备信息交换单位来分

字符设备

块设备

3. 按设备共享属性来分

独享设备

共享设备(打印机,U盘)

四:设备工作原理

五:Linux设备操作

设备或文件操作两种方式:

用户编程接口 API

系统调用

六:系统调用

系统调用是操作系统提供给用户的一组“特殊”接口

系统调用并非直接和程序员或系统管理员直接打交道,而是通过软中断的方式向内核提交请求,从而获取内核函数的服务入口(系统调用表)

系统调用让系统从用户空间进入内核空间内运行,运行后将结果返回给应用程序(内核态->用户空间)

七:系统调用和系统API等区别

1. 系统API

主要是通过C库libc来实现,程序员多采用这种方式与内核交互,这些API通过系统调用来实现

2. 系统命令

系统管理员采用系统命令与内核交互,是一个可执行文件,通过系统API及系统调用来实现

3. 外壳程序

一系列系统命令和SHELL脚本共同组合成的程序。

八:函数库调用 与 系统调用

九:C库的文件操作

十:文件系统调用

open系统调用

read系统调用

write系统调用

create系统调用

close系统调用

mkdir系统调用

十一:文件描述符fd

每个进程PCB结构中有文件描述符指针,指向files_struct的文件描述符表,记录每个进程打开的文件列表

系统内核不允许应用程序访问进程的文件描述符表,只返回这些结构的索引即文件描述符ID(File Description)给应用程序

Linux系统中,应用程序通过这些文件描述符来实现让内核对文件的访问

每个进程能够访问的文件描述符是有限制的,通过#ulimit –n可以查看

十二:特殊文件描述符号

标准输入STDIN_FILENO

标准输出STDOUT_FILENO

标准错误STDERR_FILENO

每个进程被加载后,默认打开0,1,2这三个文件描述符

十三:open系统调用

open写法一: 

有几种方法可以获得允许访问文件的文件描述符。最常用的是使用open()(打开)系统调用

函数原型

int open(const char *path, int flags);

参数

path :文件的名称,可以包含(绝对和相对)路径

flags:文件打开模式

返回值

打开成功,返回文件描述符;

打开失败,返回-1

open写法二:  

函数原型

int open(const char *path, int flags,mode_t mode);

参数

path :文件的名称,可以包含(绝对和相对)路径

flags:文件打开模式

mode:  用来规定对该文件的所有者,文件的用户组及系统中其他用户的访问权限,则文件权限为:mode&(~umask)

返回值

打开成功,返回文件描述符;

打开失败,返回-1

十四:打开文件的方式

所有这些标志值的符号名称可以通过#include<fcntl.h>访问

十五:访问权限

十六:文件打开示例

#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
void main()
{
	int outfd = 0;
	outfd = open(“myfile",O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRGRP);
	if(outfd==-1)
	{
		perror(“fail to open file\n”);
		exit(-1);
	}
	else
	{
		perror(“success to open file\n”); 
	}
	close(outfd); //关闭文件描述符
}

十七:关闭文件close

将进程中fd对应的文件描述表结构释放

函数原型:

int close(int fd);

函数参数:fd :要关闭的文件的文件描述符

返回值

如果出现错误,返回-1

调用成功返回0

十八:典型的字符设备

控制终端tty:tty是控制终端设备文件的统称,代表正控制着系统的终端

伪终端pty(pseudo tty):由pty master和pty slave构成;图形终端和远程控制终端都是pty

控制台终端console:主机的显示器称为控制台终端console,当在控制台终端登录时,tty1是虚拟终端,使用ALT+F(1-6)来切换虚拟终端tty[1~6],tty0是代表当前所使用的虚拟终端(tty?)的别名

串口终端ttyS[n]

控制终端tty

tty 表示正在控制系统的终端

十九:write系统调用

用write()系统调用将数据写到一个文件中

函数原型:

int write(int fd,void *buf,size_t nbytes);

函数参数:

fd :要写入的文件的文件描述符

buf:  指向内存块的指针,从这个内存块中读取数据写入到文件中

nbytes: 要写入文件的字节个数

返回值

如果出现错误,返回-1

如果写入成功,则返回写入到文件中的字节个数

write()示例如下:

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
void main()
	int outfd = 0, r_size = 0;
	char buf[ ] = “Hello world!!”;
	outfd = open(“first”,O_WONLY | O_TRUNC | O_CREAT,S_IRWXU);
	if(outfd>0)
	{
		r_size = write(outfd,buf,sizeof(buf));
		if(r_size>0)
		{
			printf(“write data to file success”);
		}	
		close(outfd);		
	}
}

二十:read系统调用

一旦有了与一个打开文件描述相连的文件描述符,只要该文件是用O_RDONLY或O_RDWR标志打开的,就可以用read()系统调用从该文件中读取字节

函数原型: 

int read(int fd, void *buf, size_t nbytes);

参数

fd :想要读的文件的文件描述符

buf:  指向内存块的指针,从文件中读取来的字节放到这个内存块中

nbytes: 从该文件复制到buf中的字节个数

返回值

如果出现错误,返回-1

返回从该文件复制到规定的缓冲区中的字节数,文件结束,返回0否则

二十一:close系统调用

为了重新利用文件描述符,用close()系统调用释放打开的文件描述符

函数原型:

int close(int fd);

函数参数:fd :要关闭的文件的文件描述符

返回值

如果出现错误,返回-1

调用成功返回0

二十二:终端间交互

…
        int fd1,fd2;
        char buf[80];
        fd1 = open("/dev/tty0",O_RDONLY);
        if(fd1==-1)
        {
                printf("can't open dev file\n");
                exit(-1);
        }
        fd2 = open("/dev/tty",O_WRONLY);
        if(fd2==-1)
        {
                printf("can't open dev file\n");
                exit(-2);
        }

        while(1)
        {
                memset(buf,0x00,80);
                read(fd1,buf,10);
                if(strcmp(buf,"exit\n")==0) break;
                write(fd2,buf,10);
        }
…

在secure CRT的伪终端下运行该段代码,然后在控制台终端下输入命令,会是什么结果? 

二十三:文件的随机读写

到目前为止的所有文件访问都是顺序访问。这是因为所有的读和写都从当前文件的偏移位置开始,然后文件偏移值自动地增加到刚好超出读或写结束时的位置,使它为下一次访问作好准备。

有个文件偏移这样的机制,在Linux系统中,随机访问就变得很简单,你所需做的只是将当前文件移值改变到有关的位置,它将迫使一次read()或write()发生在这一位置。(除非文件被O_APPEND打开,在这种情况下,任何write调用仍将发生在文件结束处)

二十四:lseek系统调用

功能说明:通过指定相对于开始位置、当前位置或末尾位置的字节数来重定位 curp,这取决于 lseek() 函数中指定的位置

原型:

off_t lseek (int  fd,    off_t offset,   int base);

参数分别是:需设置的文件标识符、偏移量、搜索的起始位置

返回值:返回新的文件偏移值

base 表示搜索的起始位置,有以下几个值:(这些值定义在<unistd.h>) 

二十五:chmod和fchmod系统调用

功能说明:用来改变给定路径名pathname的文件的权限位 

原型

int  chmod (char *pathname, mode_t mode);

参数分别是:文件的路径名、权限位

int  fchmod (int  fd, mode_t mode);

参数分别是:文件描述符、权限位

返回值:调用成功返回0,失败返回-1

二十六:chown和fchown系统调用

功能说明:用来改变文件所有者的识别号(owner id)或者它的用户组识别号(group ID)

原型:

int  chown (char *pathname, uid_t owner,gid_t group);

 参数分别是:文件的路径名、所有者识别号、用户组识别号

int  fchown (int  fd, uid_t owner,gid_t group);

参数分别是:文件描述符、所有者识别号、用户组识别号

返回值:调用成功返回0,失败返回-1 

二十七:mkdir系统调用

功能说明:用来创建一个称为pathname的新目录,它的权限位设置为mode

原型:

int  mkdir(char *pathname,mode_t mode);

参数分别是:文件的路径名、权限位

返回值:调用成功返回0,失败返回-1 

二十八:rmdir系统调用

功能说明:删除一个空目录

原型:

int  rmdir(char *pathname);

参数是:文件的路径名

返回值:调用成功返回0,失败返回-1

二十九:目录访问

目录访问写法一: 

功能说明:打开一个目录 

原型:

DIR*  opendir(char *pathname);

参数是:文件的路径名

返回值:
打开成功,返回一个目录指针

打开失败,则返回0

目录访问写法二:  

功能说明:访问指定目录中下一个连接的细节

原型:

struct  dirent* readdir ( DIR  *dirptr);

参数是:目录指针

返回值:
返回一个指向dirent结构的指针,它包含指定目录中下一个连接的细节;

没有更多连接时,返回NULL

目录信息结构体:

struct dirent
  {
      long d_ino;                 /* 目录i结点编号 */
      off_t d_off;                /* 目录文件开关至此目录进入点的位移 */
      unsigned short d_reclen;    /* d_name的长度 */
      char d_name [NAME_MAX+1];   /* 以NULL结尾的文件名 */
  }

如果调用opendir打开某个目录之后,第一次调用readdir函数,则返回的是该目录下第一个文件的信息,第二次调用readdir函数返回该目录下第二个文件的信息,依此类推。如果该目录下已经没有文件信息可供读取,则返回NULL。

目录访问写法三:

功能说明:关闭一个已经打开的目录
原型:
int closedir (DIR  *dirptr);

参数是:目录指针

返回值:调用成功返回0,失败返回-1
目录操作示例:
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
int my_read_dir(const char *path){
	DIR * dir;
	struct dirent *ptr;
	if(( dir = opendir(path)) == NULL ){
		return -1;
	}
	while(( ptr = readdir(dir)) != NULL ){
		printf("file name: %s\n",ptr->d_name);
	}
	closedir(dir);
	return 0;
}
int main(){
	if(my_read_dir(argv[1]) < 0 ){
		exit(1);
	}
	return 0;
}

三十:文件纪录锁

什么是文件锁:当有多个进程同时对某一文件进行操作时,就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。
Linux中文件记录锁可以对文件某一区域进行文件记录锁的控制。它是通过fcntl函数来实现的。

三十一:fcntl函数

功能说明:管理文件记录锁的操作 

原型:

#include <unistd.h>
#include <fcntl.h>
int fcntl (int fd,int cmd,struct flck *lock);
参数:fd:文件描述符;
          cmd:功能符号;
       (F_SETLK用来设置或释放锁;
           F_GETLK用来获得锁信息;)
           lock:存储锁信息的结构体指针;
返回值:调用成功返回0,失败返回-1 

锁信息结构体:

 struct flock
  {
      short  l_type;                 /* 锁的类型 */
      short  l_whence;           /* 偏移量的起始位置: */
      off_t   l_start;                /* 从l_whence的偏移量 */
      off_t   l_len;                  /* 从l_start开始的字节数 */
	 pid_t  l_pid;                  /* 锁所属进程ID(一般不用) */
  }

l_type有F_RDLCK读锁、F_WRLCK写锁及F_UNLCK空锁。

l_whence有SEEK_SET、SEEK_CUR和SEEK_END。

l_len为0时表示从起点开始直至最大可能位置为止。

fcntl()示例: 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(){
	int fd;
	struct flock lock;
	if((fd = open("example",O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) == -1){
		printf("open file error\n");
		return -1;
	}
	memset(&lock,0,sizeof(struct flock));
	lock.l_start = 0;
	lock.l_whence = SEEK_SET;
	lock.l_len = 0;
    if(fcntl(fd,F_GETLK,&lock) == 0){
		if(lock.l_type != F_UNLCK){
			printf("lock can not by set in fd\n");
		}else{
			lock.l_type = F_WRLCK;
			if(fcntl(fd,F_SETLK,&lock) == 0)
				printf("set write lock success!\n");
			else
				printf("set write lock fail!\n");
			getchar();
			lock.l_type = F_UNLCK;
			fcntl(fd,F_SETLK,&lock);
		}
	}
	close(fd);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_56051805/article/details/127234888