Linux系统编程学习笔记(4)-对终端进行控制

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。

猜你喜欢

转载自blog.csdn.net/qq_34561506/article/details/78286443