Linux中的设备
Linux系统中,设备就像文件,也可以对设备进行读写操作,与文件类似,每个设备也都有一个文件名,一个i-节点,一个文件所有者,一个权限为和最后修改时间。特别的是,终端在Linux中也体现为文件,可以通过命令:
$ tty
/dev/pts/1
查看终端号,在其他终端(或是程序)对这个终端进行些操作,可以在此终端读取到写的内容.
与不同文件或文件夹不同的是,终端设备的权限为首字母为“c”(目录为d), c表示终端文件其实是以字符为单位进行传送的设备。
另一个不同之处在于,输入如下命令:
$ ls -li /dev/pts/1
或是
$ ls -li /dev/pts/2
得到的内容中,文件的大小号一致,都为136。
对于设备来讲,文件大小位储存的并不是文件的字节数,而是指向内核子程序的指针, 内核内的这种传输设备数据的子程序被称为设备驱动程序。对于终端/dev/pts/2, 136称之为主设备好, 2称之为从设备号。主设备号确定处理该设备实际的子程序, 而从设备号被作为参数传输到该子程序。
一个简单的向终端写内容的程序如下:
#include <iostream>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFSIZ 100
using namespace std;
int main(int ac, char *av[])
{
int fd;
char buf[BUFSIZ];
if(ac != 2){
fprintf(stderr, "usage: write0 ttyname\n");
exit(1);
}
fd = open(av[1], O_WRONLY);
if(fd == -1){
perror(av[1]);
exit(1);
}
while(fgets(buf, BUFSIZ, stdin) != NULL)
if(write(fd, buf, strlen(buf)) == -1)
break;
close(fd);
其中函数
fgets()
用来从终端读取数据,stdin为标准输入。
连接的属性
调用系统函数open,创建文件描述符,会在进程与文件(设备)之间创建一个属性,系统根据这些属性来判断如何对文件进行读取或读取的形式如何。
磁盘连接的属性
磁盘主要的两个属性为缓冲与自动添加模式,下面将进行详细介绍。
属性1:缓冲
前面已经提到过,进程对磁盘文件的读写实际上述读取缓冲区的内容,修改此属性的步骤如下:
#include <fcntl.h>
int s;
s = fcntl(fd, F_GETEL);
s |= O_SYNC;
result = fcntl(fd, F_SETEL, s);
if(result == 1)
perror("setting SYNC");
系统函数调用方式如下:
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
/********************************
param fd 需控制的文件描述符
param cmd 需进行的操作
param arg 操作的参数
param loak 锁信息
return -1 遇到错误
other 依操作而定
*********************************/
int result = fcntl(int fd, int cmd);
int result = fcntl(int fd, int cmd, long arg);
int result = fcntl(int fd, int cmd, struct flock *lockp);
fcntl函数在指定的文件上执行操作cmd。arg代表操作cmd所使用的一个参数。上例中使用的操作位O_SYNC即告诉内核, 对write的调用仅能在数据写入实际的硬件时才能返回,而不是在数据复制到内核缓冲时就执行默认的返回操作。
属性2:自动添加模式
文件描述符的另一个属性是自动添加模式。自动添加模式对于若干个进程在同一时间写入文件很有用。设置自动添加模式的的文件描述符位为O_APPEND,此位被开启后,对于每个write的调用会自动调用lseek将内容添加到文件的末尾。启动自动添加模式的代码如下:
#include <fcntl.h>
int s;
s = fcntl(fd, F_GETEL);
s |= O_APPEND;
result = fcntl(fd, F_SETET, s);
if(result == -1)
perror("setting APPEND");
else
write(fd, &rec, 1);
另一种设置文件描述符属性的方法是使用open函数,具体可参照学习笔记第一部分:
http://blog.csdn.net/qq_34561506/article/details/76648581
终端连接属性
终端的属性较多,从波特率,是否回显信息,到键盘上对应的字符表示哪种指令。最简单的命令要数stty命令,可以直接在终端设置属性。例如:
$ stty erase x
就将x设置成了删除键。
程序想要该表终端驱动程序的设置要使用系统函数tcgetattr()与tcsetattr()来读取属性并设置属性。
#include <termios.h>
#include <unistd.h>
/********************************
param fd 与终端相连的文件描述符
param info 指向终端结构的指针
result -1 遇到错误
0 成功返回
*********************************/
int result = tcgetattr(int fd, struct termios *info)
tcgetattr函数从fd中获取当前设置, 并把它复制到info指针所指的结构中。
#include <termios.h>
#include <unistd.h>
/********************************
param fd 与终端相连的文件描述符
param when 改变设置的时间
param info 指向终端结构的指针
result -1 遇到错误
0 成功返回
*********************************/
int result = tcsetattr(int fd, int when, struct termios *info)
when的允许值如下:
- TCSANOW 立即更新驱动程序设置
- TCSADRAIN 等待直到驱动程序队列中的所有输出都被传送到终端,然后在更新
- TCSAFLUSH 等待直到驱动程序队列中的所有输出都被传送出去,然后释放所有队列中的输入数据,并进行一定的变化。
结构体termios中的内容如下:
struct termios{
tcflag_t c_iflag
tcflag_t c_oflag
tcflag_t c_cflag 设置奇偶性
tcflag_t c_lflag 显示回显位,终端是否回显内容
cc_t c_cc[NCCS] 控制字符
speed_t c_ispeed 波特率
speed_t c_ospeed
}
具体操作:
测试位: if(flagset & MASK)
置位: flagset |= MASK
清除位: flagset &= ~MASK
常用的形式如下:
#include <termios.h>
struct termios attribs;
tcgetattr(fd, &settigns);
settings.c_lflag |= ECHO;
tcsetattr(fd, TCSANOW, &settings);
代码示例
int get_ok_char(){
int c;
while ((c = getchar()) != EOF&&strchr("yYnN", c) == NULL)
;
return c;
}
int get_response(char *question, int maxtries){
int input;
printf("%s (y/n)", question);
fflush(stdout);
while(1){
sleep(SLEEPTIME);
input = tolower(get_ok_char());
if(input == 'y')
return 0;
if(input == 'n')
return 1;
if(maxtries-- == 0)
return 2;
BEEP;
}
}
int set_cr_noecho_mode(){ //将标准输入设置为无回显
struct termios ttystate;
tcgetattr(0, &ttystate);
ttystate.c_lflag &= ~ICANON;
//ttystate.c_lflag &= ~ECHO;
ttystate.c_cc[VMIN] = 0;
ttystate.c_cc[VTIME] = 20;
tcsetattr(0, TCSANOW, &ttystate);
}
int set_nodelay_mode(){
int termflags;
termflags = fcntl(0, F_GETFL); //获取文件描述词
//termflags |= O_NDELAY;
fcntl(0, F_SETFL, termflags);
}
int tty_mode(int how){
static struct termios original_mode;
static int original_flags;
if(how == 0){
tcgetattr(0, &original_mode); //获取终端属性
original_flags = fcntl(0, F_GETFL); //读取文件描述词
}
else{
tcsetattr(0, TCSANOW, &original_mode); //复原终端属性
fcntl(0, F_SETFL, original_flags); //设置文件描述词标志
}
}
int main(){
int response;
tty_mode(0);
set_cr_noecho_mode();
set_nodelay_mode();
response = get_response(ASK, TRIES);
tty_mode(1);
return response;
}
上述代码的主要目的是测试改变终端属性之后的终端操作模式,其中
ttystate.c_lflag &= ~ICANON
目的是取消终端的输入缓冲模式,具体体现为在终端输入后,不需要按回车键程序就开始处理输入字符,同时与之匹配的是
ttystate.c_cc[VMIN] = 0;
终端设置为一次处理一个字符。
ttystate.c_lflag &= ~ECHO
会消除终端回显功能。
非阻塞输入
上述代码体现了两种非阻塞的输入模式,分别为调用系统函数fcntl与设置等待时间VTIME
系统函数fcntl
fcntl函数的功能是根据文件描述词来操作文件的特性,用法如下:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
param:
fd:文件描述词
cmd:操作命令
arg:供命令使用的参数
lock:锁定文件时使用的参数
此次使用的设置非阻塞操作格式如下:
int termflags;
termflags = fcntl(0, F_GETFL); //获取文件状态标志
termflags |= O_NDELAY; //设置终端为非阻塞
fcntl(0, F_SETFL,termflags); //设置文件状态标志
设置延迟时间VTIME
在终端状态结构体中,VTIME数据为超时时间设置,单位为ms,但使用此方法是有讲究的,需要和VMIN同时使用,VMIN用于设置读取字符的最小数量。
1.当VTIME>0,VMIN>0时。read(或者其他读取调用)调用将保持阻塞直到读取到第一个字符,读到了第一个字符之后开始计时,此后若时间到了VTIME或者时间未到但已读够了VMIN个字符则会返回;若在时间未到之前又读到了一个字符(但此时读到的总数仍不够VMIN)则计时重新开始。
2. 当VTIME>0,VMIN=0时。read调用读到数据则立即返回,否则将为每个字符最多等待VTIME时间。
3. 当VTIME=0,VMIN>0时。read调用一直阻塞,直到读到VMIN个字符后立即返回。
4. 若在open或fcntl设置了O_NDELALY或O_NONBLOCK标志,read调用不会阻塞而是立即返回,那么VTIME和VMIN就没有意义,效果等同于与把VTIME和VMIN都设为了0。