Linux程序设计(上)

系列文章目录

文章目录

  • 系列文章目录
  • 前言
  • 一、unix, linux, GNU, POSIX
    • Linux程序
  • 二、shell
    • shell语法
      • 1.变量
      • 2.语句
    • 函数
    • 命令
    • 命令的执行
    • dialog工具
    • --
  • 三、文件操作
    • 1. Linux 文件结构
    • 2. 系统调用和设备驱动程序
    • 3. 库函数
    • 4. 底层文件访问
    • 5. 标准I/O库
    • 6.格式化输入输出
    • 7. 文件和目录维护
    • 8. 扫描目录
    • 9. 错误处理
    • 10. /proc文件系统
    • 11. fcntl, mmap
  • 四、linux 环境
    • 1. 命令行解析函数
    • 2. 环境变量
    • 3. 时间和日期
    • 4. 临时文件
    • 5. 用户信息
    • 6. 主机信息
    • 7. 日志
    • 8. 资源和限制
  • 五、终端
    • /dev/tty
    • 终端驱动程序与其接口
      • 输入模式
      • 输出模式
      • 控制模式
      • 本地模式
      • 特殊字符控制
      • -
    • 终端输出
      • 终端控制程序示例
      • 检测击键动作
    • 虚拟控制台
    • 伪终端
    • AI
  • 六、curses函数库管理基于文本的屏幕
  • 七、数据管理
    • 内存管理
    • 文件锁
    • dbm数据库
  • 八、MySQL
  • 十五、套接字
    • 1. 套接字属性
    • 2. 套接字地址
    • 3. 套接字命名
    • 4. 创建套接字队列
    • 5. 接受连接
    • 1. 请求连接
    • 2. 使用完成后应关闭sockfd
    • 网络信息
    • 因特网守护进程xinetd/inetd
    • 套接字选项
    • select系统调用
    • 多客户服务器程序
    • 数据报(UDP)


前言


一、unix, linux, GNU, POSIX

通用许可证(GPL)条款下发布的一些主要GNU项目软件:

  • GCC: GNU编译器集,包括GNU C编译器。
  • G++: C++编译器,是GCC的一部分。
  • GNU make: UNIX make命令的免费版本。
  • Bison: 与UNIX yacc兼容的语法分析程序生成器。
  • bash: 命令解释器(shell的一种具体实现)。
  • GNU Emacs: 文本编辑器及环境。

通过以上一些列的可用的自由软件+Linux kernel,可以说:创建一个GNU的、自由的类UNIX系统的目标已经通过Linux系统实现了。

Linux内核+一系列工具程序=Linux发行版

POSIX:
POSIX(Portable, Operating System Interface)是基于UNIX或类UNIX操作系统的一系列操作系统接口标准。标准定义了常用接口(open, write…)和通用工具(cd, ls…)。POSIX在源代码级别支持应用程序的可移植性,因此应用程序可以构建为在任何符合POSIX的操作系统上运行。

可用的编程语言:
Linux程序设计(上)_第1张图片

Linux程序

  1. 可执行文件
    计算机可以直接运行
  2. 脚本文件
    由解释器执行

echo $PATH

  • /bin: 二进制文件目录,用于存放系统启动时用到的程序。
  • /usr/bin: 用户二进制文件目录,用于存放用户使用的标准程序。
  • /usr/local/bin: 本地二进制文件目录,用于存放软件的安装的程序。

可选的操作系统组件和第三方应用程序可能被安装在 /opt 目录下

二、shell

shell是一个普通的用户程序,它解析命令行中的字符串并执行命令。shell是用户程序,并非内核的一部分。经典的shell是 bash,一般Linux都会预装。csh也是个命令解释器。
Linux程序设计(上)_第2张图片

Linux程序设计(上)_第3张图片

ls > out.txt   # 重定向,覆盖文件
ls >> out.txt  # 重定向,追加
# 将标输出误和标准错误分别重定向到不同的文件中
ls > out.txt 2> out2.txt

# 如果想把两组输出都重定向到一个文件中,可以用>&操作符来结合两个输出
# 将1,2都重定向到同一个 out.txt  需注意操作符出现的顺序。
# “将标准错误重定向到与标准输出相同的地方”
ls > out.txt 2>&1  # 

# Linux通用“回收站” /dev/null 将所有输出丢弃
ls > /dev/null 2>&1

查看系统中运行的所有进程的名字,但包括shelll本身

ps -xo comm|sort |uniq | grep -v sh | less
# sort按照字母顺序排序,uniq:去除同名的 grep -v sh删除名为sh的进程

bash通配符:

  • *:匹配任意个字符
  • ?:单个字符
  • [set]:匹配方括号中任何一个字符
  • [^set]:上面取反
  • {}: ls my_{finger, toe}s将显示 my_fingers my_toes

shell语法

#!/bin/bash
#!/bin/env python 是更为通用的写法

for i in *
do
	if grep -q POSIX $i
	then
		echo $file
	fi
done

exit 0  # optional exit normal

chmod +x test.bin # 所有用户都有该文件的可执行权限

1.变量

a=a  # 1.等号两边不能有空格
b='a b' #2.有空格必须用引号

echo $b $0 $1 ...

read val  # 等待用户输入字符,直到遇到...   you known what that mean
echo $val
echo '$val'
echo "$val"
echo \$val

单引号不会将$b替换为它的值,双引号就会
转移字符 \ 这shell中也有效

环境变量:
Linux程序设计(上)_第4张图片
参数变量
Linux程序设计(上)_第5张图片

2.语句

检查一个文件是否存在

if [ -f file.c ]; then  # [ 命令前后要空格 ( [ 相当于 test 关键字), 这样写then前要加 ;
# 结果为bool形
	...
fi

# or
if [ -f file.c ]
then
	...
fi

# or
if test -f file.c
then
...
fi

test命令可使用的条件类型可归为3类:字符串比较、算术比较和与文件有关的条件测试:
Linux程序设计(上)_第6张图片

echo -n agbc  # 去除换行符
read timeofday
if [ "$timeofday" = "yes" ]; then
	echo "yes"
elif [ "$timeofday" = "no" ]; then
	echo "no"
else
	echo "???"
	exit 1
fi

exit 0

for

for i in a b cd e
do
	echo $i
done

# or
for i in a b cd; do
	echo $i
done


# $(command)语法
for file in $(ls f*.sh); do
	lpr $file
done

while

while condition do
	...
done

# or
while [ -d LOGS ] ;do
    echo "ab"
    sleep 1
done

until

until condition  # 循环将反复执行直到条件为真
do
	...
done
#---------------
until who | grep "$1" > /dev/null
do
	sleep 60
done
# ring the bell
echo -e '\a'

case

case var in
	pattern [ | pattern] ...) statements;;
	pattern [ | pattern] ...) statements;;
esac
#-------------------
read timeofday

case "$timeofday" in
	yes) echo "sdf";;
	no ) echo sd;;
	n  ) echo sdf;;
	*  ) echo sjdlfkj;;
esac
#-------------------

case "$timeofday" in
	yes | y | Yes | YES ) echo "slkdjf";;
	n* | N* )             echo asdf;;
	* )                   echo jsdlkfj;;
esac
#-------------------
case "$timeofday" in
	yes | y | Yes | YES)
		echo askjdf
		echo sjldkfj
		;;
	[nN]*)
		echo sjldkfjsdfsdf
		echo sjldkfjlksjdfljsldkfjlsdjf
		;;
	[Yy] | [Yy][Ee][Ss])
		echo haha
		;;
	*)
		echo j
		echo sdl
		exit 1
		;;
esac

命令列表 逻辑结构
statement1 && statement2 && statement3 && …
&&的作用是检查前一条命令的返回值 左边为true才执行右边

statement1 || statement2 || statement3 || …
执行一系列命令直到有一条命令成功
|| 只有左边为false才执行右边,遇到true立即停止

if [ -f file1 ] && echo "hello you" && [ -f file2] && echo "hehe"
then
	echo "in if"
else
	echo "in else"
fi

#--------------------
if [ -f file1 ] || echo "hello you" || [ -f file2] || echo "hehe"
then
	echo "in if"
else
	echo "in else"
fi

#-------------------
[ -f file1 ] && command for true || command for false

语句块
将多条语句当成一条

get_confirm && {
	grep -v "$cdcatnum" &tracks_file > $temp_file
	cat $temp_file > $tracks_file
	echo
	add_record_tracks
}

函数

function_name(){
	statements
}
#------------
foo(){
	echo fucntion too is executing
}
echo script starting
foo
echo script ended
sleep 1
exit 0

函数的用法

#!/bin/bash
#!/bin/env python 是更为通用的写法

yes_or_no(){
	echo "is your name $@"
	while true
	do
		echo -n "Enter yes or no: "
		read x
		case "$x" in
			[Yy] | [Yy]es ) return0;;
			[Nn] | [Nn]o ) return1;;
			*)           echo "Answer yes or no"
		esac
	done
}

echo "Original parameter are $*"
if yes_or_no "$1"
then
	echo "Hi $1, nice name"
else
	echo Never mind
fi
exit 0

命令

  1. break

    for dir in fred*
    do
    	if [ -d "$dir" ]; then
    		break;
    	fi
    done
    
    echo first directory starting fred was $dir
    
  2. : 命令
    冒号(:)命令是一个空命令。偶尔用于简化 条件逻辑,相当于true的一个别名。由于它是内置命令,所以它运行的比true快。
    while:相当于while true无限循环

    if [ -f fred ]; then
    	:
    else
    	echo file nnooo
    fi
    
  3. continue
    类似C

  4. .命令
    source命令和.命令差不多是同义词
    source ./shell_script . ./shell_script
    通常当前shell在执行命令是会fork然后waiti子进程执行完毕,而.命令是不让shell fork

  5. echo
    echo -n "结尾没有换行"
    echo -e "转移字符有效\t\a\n等"

  6. eval
    对参数求值。有点像一个额外的$,它给出一个变量的值

    foo=10
    x=foo
    y='$'$x  #输出:$foo
    echo $y
    
    foo=10
    x=foo
    eval y='$'$x
    echo $y  #输出10
    
  7. exec
    将当前shell替换为一个不同的程序

    exec wall "Thanks for all the fish"
    

    修改当前文件描述符:

    exec 3<afile  # 文件描述符3被打开以从afile文件中读取
    
  8. exit n命令
    使脚本程序以退出码n结束运行。
    shell脚本编程中,0表示成功,1~125是脚本程序可以使用的错误代码。其余数字具有保留含义

    退出码 说明
    126 文件不可执行
    127 命令未找到
    128及以上 出现一个信号
  9. export
    export命令将作为它参数的变量导出到子shell中,并使之在子shell中有效。
    set -aset -allexport命令 将导出它之后声明的所有变量

  10. expr
    将它的参数当做一个表达式来求值。

    # 反引号`字符使x取值为命令expr $x + 1的执行结果
    x=`expr $x + 1`
    # or
    x=$(expr $x + 1)
    
  11. printf
    printf "format string" param1 param2

    printf "%s %d\t%s" "Hi there" 15 people
    # Hi there 15 people
    
  12. return

  13. set
    为shell设置参数变量

    echo the date is $(date)
    set $(date)
    echo The month is $2
    
  14. shift
    把所有参数变量左移一个位置,使$2变为$1,$3变为$2。$0不变

    while [ "$1" != "" ]; do
    	echo "$1"
    	shift
    done
    
  15. trap
    接收到信号后所采取的行动
    trap -l列出信号
    trap commond signal

    #!/bin/bash
    #!/bin/env python 是更为通用的写法
    
    trap 'rm -f /tmp/my_tmp_file_$$' INT
    echo creating file
    date > /tmp/my_tmp_file_$$
    
    echo "press interrupt (ctrl-c) to interrupt ..."
    while [ -f /tmp/my_tmp_file$$ ]; do
    	echo File exits
    	sleep 1
    done
    echo the file no longer exists
    trap INT  # 
    
  16. unset
    从环境中删除变量或函数,不能删除shell本身定义的只读变量

    foo="hello"
    echo $foo
    unset foo
    echo $foo
    
  17. 搜索相关

    • find / -name test
      find [path] [options] [tests] [actions]
    • grep
      general regular expression parser
    • 正则
    字符 含义
    ^ 指向一行的开头
    $ 指向一行的结尾
    . 任意单个字符
    [] 方括号同通配符
匹配模式 含义
[:alnum:] 字母与数字
[:alpha:] 字母
[:ascii:] ASCII字符
[:blank:] 空格或制表符
[:digit:] 数字

命令的执行

简单示例

for i in 1 2
do
	my_file_${i}
done

Linux程序设计(上)_第7张图片

dialog工具


命令行图形化工具
Linux程序设计(上)_第8张图片

类型 选项 选项 含义
dialog --title "Check me" --checklist "Pick Numbers" 15 25 3 1 "one" "off" 2 "two" "on" 3 "three" "off"
#!/bin/bash
#!/bin/env python 是更为通用的写法

dialog --title "Qestionnaire" --msgbox "welcom to my simple survey" 9 18

dialog --title "Confirm" --yesno "are you willing to take part?" 9 18
# 用环境变量$?来检查用户是否选择了yes
if [ $? != 0 ]; then
    dialog --infobox "Thank you anyway" 5 20
    sleep 2
    dialog --clear
    exit 0
fi

# 使用一个输入框来询问用户的姓名。重定向标准错误流2到临时文件_1.txt,然后再将其放入变量Q_NAME中
dialog --title "Questionnaire" --inputbox "please enter your name" 9 30 2>_1.txt
Q_NAME=$(cat _1.txt)

# 用户选择的菜单项编号将被保存到临时文件_1.txt中,同时这个结果被放入变量Q_MUSIC中
dialog --menu "$Q_NAME, what music do you like best?" 15 30 4 1 "Classical" 2 "Jazz"\
    3 "Country" 4 "Other" 2>_1.txt
Q_MUSIC=$(cat _1.txt)

if [ "$Q_MUSIC" == "1" ]; then
    dialog --title "Likes Classical" --msgbox "Good choice!" 12 25
else
    dialog --title "Doesn't like Classical" --msgbox "Shame" 12 25
fi

sleep 2
dialog --clear
exit 0


三、文件操作

  • 文件和设备
  • 系统调用
  • 库函数
  • 底层文件访问
  • 管理文件
  • 标准I/O库
  • 格式化输入和输出
  • 文件和目录的维护
  • 扫描目录
  • 错误及其处理
  • /proc文件系统
  • fcntl和mmap

1. Linux 文件结构

Linux一切皆文件
访问不同类型的设备和访问文件一样

对于文件操作,大多数情况下,需要知道5个基本的函数——open, close, read, write, ioctl

文件,除了本身包含的内容以外还包含一个名字和一些属性信息(即,“管理信息”,包括修改时间,访问权限等)。这些属性被保存在文件的inode中,它是文件系统中的一个特殊的数据块。
目录是用于保存其它文件的节点号和名字的文件。

UNIX和Linux中比较重要的设备文件有3个:/dev/console, /dev/tty, /dev/null

  1. /dev/console
    这个设备代表的是系统控制台
  2. /dev/tty

    如果一个进程有控制终端的话,那么特殊文件/dev/tty就是这个控制终端(键盘和显示屏,或键盘和窗口)的别名(逻辑设备)。如,由系统自动运行的进程和脚本就没有控制终端,所以它们不能打开/dev/tty。 /dev/tty/目录下存放所有的逻辑终端
  3. /dev/null
    /dev/null文件是空(null)设备。所有写向这个设备的输出都将被丢弃,而读取这个设备会立刻返回一个文件尾标志。

/dev目录中的其他设备包括:硬盘、软盘、通信端口、声卡以及一些代表系统内部工作状态的设备。

2. 系统调用和设备驱动程序

操作系统内核,由一组设备驱动程序组成。

ioctl: 把控制信息传递给设备驱动程序。用于提供一些与特定硬件设备有关的必要控制,所以它的用法随设备的不同而不同。

3. 库函数

函数库封装的系统调用,提供更高层次的用法。如stdio.h

用户程序、函数库、系统调用、内核、设备驱动以及硬件之间的关系

Linux程序设计(上)_第9张图片

4. 底层文件访问

文件描述符相当与一个索引,它指向操作系统的一个对象

write
  的作用是把缓冲区buf的年n个字节写入与文件描述符fildes关联的文件中。它返回实际写入的字节数。如果文件描述符有错误或者底层的设备驱动程序对数据块长度比较敏感,该返回值可能会小于n。如果返回0,则表示未写入任何数据;如果返回-1,表示调用出错,错误码会保存在全局变量errno里。

#include 
size_t wirte(int fd, const void* buf, size_t nbytes);

read
  的作用是从与文件描述符fd关联的文件里读入n个字节的数据,并把它们放到数据区buf中。它返回实际读入的字节数,这可能会小于请求的字节数。如果返回0,则表示未读入任何数据,已达到了文件尾;如果返回-1,表示调用出错,错误码会保存在全局变量errno里。

open
  建立了一条到文件或设备的访问路径。如果调用成功,它将返回一个可以被read、write和其他系统调用使用的文件描述符。这个文件描述符是唯一的,它不会与任何其他运行中的进程共享 。如果两个进程同时打开一个文件,它们会分别得到两个不同的文件描述符。如果它们都对文件进行写操作,那么它们会各写各的,它们分别接着上次离开的位置继续往下写。它们的数据不会交织在一起,而是彼此互相覆盖。两个程序对文件的读写位置(偏移值)不同。可以通过使用文件锁来防止出现冲突。

#include 
#incldue <sys/types.h>
#incldue <sys/stat.h>

int open(const char* path, int oflags);
int open(const char* path, int oflags, mode_t mode);
--oflags: 文件访问模式
		- O_RDONLY
		- O_WRONLY
		- O_RDWR
		- O_APPEND
		- O_TRUNC: 覆盖
		- O_CREAT: 如果需要,就按参数mode中给出的访问模式创建文件
		- O_EXCL: 与O_CREAT一起使用,确保调用者创建出文件。open调用是一个原子操作,也即是在说,
它只执行一个函数调用。使用这个可选模式可以防止两个程序同时创建同一个文件。如果文件已存在,open调用将失败。
当使用O_CREAT时,必须使用三个参数格式的open调用。第三个参数mode是几个标志位按位或后得到的,这些标志位在头文件sys/stat.h中:
  • S_IRUSR: 读,属主
  • S_IWUSR: 写,属主
  • S_IXUSR: 执行,属主
  • S_IRGRP: 读,属组
  • S_IWGRP:写,属组
  • S_IXGRP:执行,属组
  • S_IROTH: 读,其他
  • S_IWOTH: 写,其他
  • S_IXOTH: 执行,其他
    在程序中创建的文件的权限是有mode参数和umask值共同决定的,umask是权限掩码。这样做并不能组织程序或用户在随后使用chmod命令或chmod系统调用来改变文件权限。

close
  终止文件描述符与其对应文件之间的关联,并释放资源。成功0,失败-1。

ioctl
  大杂烩。它提供了一个用于控制设备及其描述符行为和配置底层服务的接口。终端、文件描述符、套接字甚至磁带都可有为它们定义的ioctl。

#include 
int ioctl(int fd, int cmd, ...);

理论上,下面这两段程序所花费的时长将不同,其主要耗费在了系统调用上,所以对其使用进行优化是有必要的。

int main()
{
    int fd_src;
    fd_src = open("txt.txt" , O_RDONLY);
    if (fd_src < 0) perror("open, txt.txt");
    char buf[1024] = {'\0'};
    size_t rdsz = 0;

    int fd_dst;
    fd_dst = open("txt2.txt", O_WRONLY | O_CREAT, 0777);

    while ( (rdsz = read(fd_src, buf, 1024)) > 0 )
    {
        if (write(fd_dst, buf, rdsz) < rdsz)
        {
            perror("write");
            exit(0);
        }
    }
    close(fd_src);
    close(fd_dst);
}

int main()
{
    int fd_src;
    fd_src = open("txt.txt" , O_RDONLY);
    if (fd_src < 0) perror("open, txt.txt");
    char buf[1] = {'\0'};
    size_t rdsz = 0;

    int fd_dst;
    fd_dst = open("txt2.txt", O_WRONLY | O_CREAT, 0777);

    while ( (rdsz = read(fd_src, buf, 1)) > 0 )
    {
        if (write(fd_dst, buf, rdsz) < rdsz)
        {
            perror("write");
            exit(0);
        }
    }
    close(fd_src);
    close(fd_dst);
}

lseek
  对文件描述符的读写指针进行设置。

#include 
#include 

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

fstat, stat, lstat
  返回与打开的文件描述符相关的文件的状态信息,该信息将会写到一个buf结构中,buf的地址以参数形式传递给fstat

#include 
#include 
#include 

int fstat(int fd, struct stat *buf);
int stat(const char* path, struct stat *buf);
int lstat(const char* path, struct stat *buf);

dup, dup2
  复制文件描述符,使得多个fd指向同一个对象

#include 
int dup(int fd);
int dup2(int fd, int fd2);

5. 标准I/O库

  • fopen, fclose
  • fread, fwrite
  • fflush-
  • fseek-
  • fgetc, getc, getchar
  • fputc, putc, putchar
  • fgets, gets
  • printf, fprintf, sprintf
  • scanf, fscanf, sscanf
  1. fopen
    FILE* fopen(const char* filename, const char* mode);
    
  2. fread
    size_t fread(void*ptr, size_t size, size_t nitems, FILE* stream);
    
  3. fwrite
    size_t fwrite(const void*ptr, size_t size, size_t nitems, FILE* stream);
    
  4. fclose
    int fclose(FILE*stream);
    
  5. fflush
    int fflush(FILE* stream);
    
  6. fseek
    int fseek(FILE* stream, long int offset, int whence);
    
  7. fgetc, getc, getchar
  8. fputc, putc, putchar
  9. fgets, gets
    fgets从输入文件流读取一个字符串,直到遇到换行符或已经传输了n-1个字符或到达文件尾。
    gets类似上面,只不过它从标准输入读取数据并丢弃换行符
    char* fgets(char*s, int n, FILE*stream);
    char* gets(char* s);
    

6.格式化输入输出

printf, fprintf, sprintf
scanf, fscanf, sscanf

fgetpos: 获得文件流的当前位置
fsetpos: 设置文件流的当前位置
ftell: 返回文件流当前位置的偏移值
rewind: 重置文件流里的读写位置
freopen:重新使用一个文件流
setvbuf:设置文件流的缓冲机制
remove:相当于unlink函数,但如果它的path参数是一个目录的话,其作用就相当于rmdir

int ferror(FILE* stream);
int feof(FILE* stream);
void clearerr(FILE* stream);
int fileno(FILE* stream);
FILE* fdopen(int fd, const char*mode);

7. 文件和目录维护

  1. chmod – syscall

    int chmod(const char* path, mode_t mode);
    
  2. chown – syscall

    int chown(const char* path, uid_t owner, gid_t group);
    
  3. unlink, link, symlink – syscall
    unlink系统调用删除一个文件的目录项并减少它的连接数。如果一个文件的连接数减少到0,并且没有进程打开它,这个文件就会被删除。(目录项总是被立刻删除,但文件所占用的空间要等到最后一个进程关闭它之后才会被系统回收)先用open创建一个文件然后对其调用unlink,可以用来创建临时文件,这些文件只有在被打开的时候才能被程序使用,当程序退出并且文件关闭时它们就会被自动删除掉
    link将创建一个指向已有文件path1的新链接。新目录项由path2给出。可以通过symlik创建符号链接。

    int unlink(const char* path);
    int link(const char* path1, const char* path2);
    int symlink(const char* path1, const char* path2);
    
  4. mkdir, rmdir – syscall

    #include 
    #include 
    int mkdir(const char* path, mode_t mode);
    #include 
    int rmdir(const char* path);
    
  5. chdir – syscall, getcwd – function
    chdir用于切换目录
    getcwd用于显示当前工作目录

    #include 
    int chdir(const char* path);
    char* getcwd(char* buf, size_t size);
    

8. 扫描目录

文件流(FILE*)
目录流(DIR*)

  • opendir, closedir
  • readdir
  • telldir
  • seekdir
  1. opendir – function

    #include 
    #include 
    DIR* opendir(const char* name);
    
  2. readdir – function
    每次调用该函数都会返回目录项中的下一项,如果发生错误或到达目录尾放回NULL

    #include 
    #include 
    struct dirent* readdir(DIR* dirp);
    
  3. telldir – function
    telldir函数的返回值记录着一个目录流里的当前位置,可以再随后的seekdir中利用这个值来重置目录扫描到当前位置。

    long int telldir(DIR* dirp);
    
  4. seekdir – function
    设置目录流的目录项指针。loc的值用来设置指针位置,它应该通过前一个telldir获得

    void seekdir(DIR* dirp, long int loc);
    
  5. closedir

    int closedir(DIR* dirp);
    

9. 错误处理

#include

strerror函数把错误代码映射为一个字符串,该字符串对发生的错误类型进行说明

perror函数也把errno变量中报告的当前错误映射到一个字符串,并把它输出到标准错误输出流

char* strerror(int errnum);

void perror(const char* s);

10. /proc文件系统

proc是伪文件系统,它提供了与内核数据结构的接口。通常挂载在/proc下。一般由系统自动挂载,也可手动挂载: mount -t proc proc /proc。/proc中的大多数文件是只读的,有些是可写的用以修改内核变量的值。

11. fcntl, mmap

fcntl对底层文件描述符提供了更多的操作方法

#include 
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);

mmap函数创建一个指向一段内存区域的指针,该内存区域与可以通过一个打开的文件描述符访问的文件的内容相关联。

#include 
// 以下两个函数,线程安全

// success: 有效地址; failure: (void*)-1, errno be set
void *mmap(void *addr, size_t len, int prot,  int flags, int fd, off_t offset);
// sucess: 0; failure:-1, errno be set
int munmap(void* addr, size_t length);
  • return: 映射起始地址

  • addr: 创建的新映射的起始地址; 如果为NULL,内核会自动选择合适的地址(推荐的方式),如果不为NULL,则内核选择它作为映射的起始地址(不推荐)

  • len: 映射的长度(必须大于0)

  • prot: 映射内存的权限。以下可安位取或,不能与文件的打开模式冲突
    PROT_EXEC:
    PROT_READ:
    PROT_WRITE:
    PROT_NONE: 不能访问,不能和以上取或

  • flags: 参数确定映射的更新对于映射同一区域的其他进程是否可见,以及更新是否会传递到底层文件。此行为由以下因素决定
    在标志中恰好包含以下值之一:

    • MAP_SHARED:
  • fd: 文件描述符(文件映射,与匿名映射相反)。调用mmap后fd可直接关闭而不影响mmap

  • offset: 被映射fd的起始地址偏移量,必须为页大小( sysconf(_SC_PAGE_SIZE)返回值 )的整数倍

NOTE:
文件以页面大小的整数被进行映射,映射尾部多余的补0,并且对多余部分的修改不会写入文件。此时增删文件的大小是未定义行为。

#include 
#include 
#include 
#include 
#include 
#include 

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    char *addr;
    int fd;
    struct stat sb;
    off_t offset, pa_offset;
    size_t length;
    ssize_t s;

    if (argc < 3 || argc > 4) {
        fprintf(stderr, "%s file offset [length]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        handle_error("open");

    if (fstat(fd, &sb) == -1)           /* To obtain file size */
        handle_error("fstat");

    offset = atoi(argv[2]);
    pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
        /* offset for mmap() must be page aligned */

    if (offset >= sb.st_size) {
        fprintf(stderr, "offset is past end of file\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 4) {
        length = atoi(argv[3]);
        if (offset + length > sb.st_size)
            length = sb.st_size - offset;
                /* Can't display bytes past end of file */

    } else {    /* No length arg ==> display to end of file */
        length = sb.st_size - offset;
    }

    addr = (char*)mmap(NULL, length + offset - pa_offset, PROT_READ,
                MAP_PRIVATE, fd, pa_offset);
    if (addr == MAP_FAILED)
        handle_error("mmap");

    s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
    if (s != length) {
        if (s == -1)
            handle_error("write");

        fprintf(stderr, "partial write");
        exit(EXIT_FAILURE);
    }

    munmap(addr, length + offset - pa_offset);
    close(fd);

    exit(EXIT_SUCCESS);
}
#include 
int msync(void* addr, size_t len, int flags);

把在该内存段的某个部分或整段中的修改写回到被映射的文件中(或者从被映射的文件里读出)

  • flags: MS_ASYVN(异步写), M_SYNC(同步写), MS_INVALIDATE(从文件中读回数据)
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct
{
    int integer;
    char string[24];
} RECORD;

#define NRECORDS (100)


int main(int argc, const char* argv[])
{
    
    RECORD record, *mapped;
    FILE *fp;

    fp = fopen("records.dat", "w+");
    for (int i = 0; i < NRECORDS; ++i) {
        record.integer = i;
        sprintf(record.string, "RECORD-%d", i);
        fwrite(&record, sizeof(record), 1, fp);
    }
    fclose(fp);

    fp = fopen("records.dat", "r+");
    fseek(fp, 43*sizeof(RECORD), SEEK_SET);
    fread(&record, sizeof(RECORD), 1, fp);

    record.integer = 143;
    sprintf(record.string, "RECORD-%d", record.integer);

    fseek(fp, 43*sizeof(RECORD), SEEK_SET);
    fwrite(&record, sizeof(RECORD), 1, fp);
    fclose(fp);

    int fd = open("records.dat", O_RDWR);
    mapped = (RECORD*)mmap(NULL, NRECORDS*sizeof(RECORD), PROT_READ|PROT_WRITE, 
                                                            MAP_SHARED, fd, 0);
    close(fd);
    mapped[43].integer = 243;
    sprintf(mapped[43].string, "RECORD-%d", mapped[43].integer);
    msync(mapped, NRECORDS*sizeof(RECORD), MS_ASYNC);
    munmap(mapped, NRECORDS*sizeof(RECORD));
    
}

四、linux 环境

  • 向程序传递参数
  • 环境变量
  • 查看时间
  • 临时文件
  • 获得有关用户和主机的信息
  • 生成和配置日志信息
  • 了解系统各项资源的限制

1. 命令行解析函数

main的参数由shell传递

命令行解析函数:

#include 

int getopt(int argc, char* const argv[], const char* optstring);
extern char* optarg;
extern int optind, opterr, optopt;

#include 
int getopt_long(int argc, char* const argv[], const char*optstring, 
				const struct option* longopts, int *longindex);
int getopt_long_only(int argc, char* const argv[], const char* optstring,
					const struct option* longopts, int* longindex);
  • optarg:选项后面的值会被赋给optarg
  • optind:所有选项+值的个数
  • optopt:有问题的字符选项
  • 返回值为选项字符

eg:

# -n   -t val

#include 
#include 
#include 

int
main(int argc, char *argv[])
{
    int flags, opt;
    int nsecs, tfnd;

    nsecs = 0;
    tfnd = 0;
    flags = 0;
    while ((opt = getopt(argc, argv, "nt:")) != -1) {
        switch (opt) {
        case 'n':
            flags = 1;
            break;
        case 't':
            nsecs = atoi(optarg);
            tfnd = 1;
            break;
        default: /* '?' */
            fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    printf("flags=%d; tfnd=%d; nsecs=%d; optind=%d\n",
            flags, tfnd, nsecs, optind);

    if (optind >= argc) {
        fprintf(stderr, "Expected argument after options\n");
        exit(EXIT_FAILURE);
    }

    printf("name argument = %s\n", argv[optind]);

    /* Other code omitted */

    exit(EXIT_SUCCESS);
}


int main(int argc, const char* argv[])
{
    int opt = -1;
    while ( (opt = getopt(argc, argv, "alhf:d:")) != -1 )
    {
        printf("-----st-------");
        printf("optind:%d, opt:%c\n", optind, optopt);
        switch (opt)
        {
        case 'a':
        case 'l':
        case 'h':
            printf("option:%c\n", opt);
            break;
        case 'f':
            printf("-f val:%s\n", optarg);
            break;
        
        default:
            printf("optind:%d, opt:%c\n", optind, optopt);
            break;
        }
        printf("-----ed-------");
        printf("optind:%d, opt:%c\n", optind, optopt);
    }
    printf("optind:%d, opt:%c\n", optind, optopt);
    
}

getopt_long

struct option longopts[] = {
	{"initialize", 0, NULL, 'i'},
	{"file", 1, NULL, 'f'},
	{"list", 0, NULL, 'l'},
	{"restart", 0, NULL, 'r'},
	{0,0,0,0}
};
while( (opt = getopt_long(argc, argv, "if:lr")) != -1 );

Linux程序设计(上)_第10张图片

2. 环境变量

#include 

char* getenv(const char* name);
int putenv(const char* string);

  getenv函数以给定的名字搜索环境中的一个字符串,并返回与该名字相关的值。若变量不存在或变量没有值,则返回null。它返回的字符串存储在静态区
  putenv函数以一个格式为“名字=值”的字符串作为参数,并将该字符串加到当前环境变量中。如果失败返回-1。

NOTE:
环境仅对程序本身有效。变量的值不会从子进程传播到父进程

#include 
#include 

int main(int argc, char *argv[])
{
    char* home_val = getenv("HOME");
    printf("%s\n", home_val);
    putenv("HELLO=YES");
    home_val = getenv("HELLO");
    printf("%s\n", home_val);
}

程序经常使用环境变量来改变它们的工作方式。

用户可在以下方式设置环境变量的值:在默认环境中设置、通过登录shell读取的.profile文件来设置、使用shell专用的启动文件(rc)或在shell命令行上对变量进行设置

#incldue <unistd.h>
extern char** environ;

3. 时间和日期

GMT 格林尼治时间 1970年1月1日0点

date命令可以获取时间和日期

UTC时间:1970年1月1日零时作为 第0秒

获取时间点:

#include 
/* Return the current time and put it in *TIMER if TIMER is not NULL.  */
// 返回秒数,跟随系统时间
time_t time (time_t *__timer) __THROW;


/* Get current value of clock CLOCK_ID and store it in TP.  */
extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __THROW;


__clock_id:CLOCK_REALTIME(系统时间),CLOCK_MONOTONIC(单调递增时钟,可用于计算时间间隔)

时间转换:

struct tm {
   int tm_sec;    /* Seconds (0-60) */
   int tm_min;    /* Minutes (0-59) */
   int tm_hour;   /* Hours (0-23) */
   int tm_mday;   /* Day of the month (1-31) */
   int tm_mon;    /* Month (0-11) */
   int tm_year;   /* Year - 1900 */
   int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
   int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
   int tm_isdst;  /* Daylight saving time */
};

// *_r thread safe
// 不带 _r 的函数返回值存储在静态区
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

// 线程安全
time_t mktime(struct tm *tm);

格式化输出时间

// 将struct tm 格式化为 字符串的形式
size_t strftime(char*s, size_t maxsize, const char*format, struct tm* timeptr);

// 读取日期和时间, 
/* Parse buf according to FORMAT and store binary time information in timeptr.
The return value is a pointer to the first unparsed character in S.  */
char* strptime(const char* buf, const char* format, struct tm* timeptr);

4. 临时文件

#include 

// 返回一个不与任何已存在文件同名的有效文件名(可能与其他程序调用该函数产生的名字同名)
// 关于线程安全等请参考手册
char* tmpnam(char*s);  // 返回值存储在静态存储区

// 直接打开一个唯一的临时文件,当对它的引用全部关闭时,该文件会被自动删除
FILE* tmpfile(void);
#include 

// unix中类似函数
char* mktemp(char* template);  
int mkstemp(char* template);  // 返回文件描述符

template参数必须是一个以6个X字符结尾的字符串,以上函数以给定的模板为基础创建一个唯一的文件名。用有效文件名字符的一个唯一组合来替换这些X字符

5. 用户信息

init进程PID=1,除了该进程外,所有Linux程序都是由其他程序或用户启动的。
程序能够通过检查环境变量和读取系统时钟来在很大程度上了解它所处的运行环境。程序也能够发现它的使用者的相关信息。
当一个用户要登录Linux时,通过用户名和密码验证,用户就可以进入一个shell。每个用户都有一个唯一的UID,Linux运行的每个程序都是以某个用户的名义在运行,都有一个关联的UID。

UID
为uid_t类型,在sys/types.h中定义,一般情况下,用户的UID值都大于100.

#include 
#include 
// 返回启动程序的用户的UID
uid_t getuid(void);
// 返回与当前用户关联的登录名
char* getlogin();

系统文件/etc/passwd 包含一个用户账号数据库。如果要获取相关信息建议使用如下相关函数:

sdf: x :1000:1000:sdf,,,:/home/sdf:/bin/bash

用户名 加密口令 UID GID 全名 家目录 默认shell
sdf x 1000 1000 sdf,,, /home/sdf /bin/bash

获取用户信息的编程接口:

#include 
#include 
struct passwd* getpwuid(uid_t uid);
struct passwd* getpwnam(const char* name);

// 依次取出文件数据项
struct passwd *getpwent();
// 终止处理过程
void endpwent(void);
// 再次调佣getpwent时将返回首x
void setpwent(void);

#include 
/* Get the real user ID of the calling process.  */
extern __uid_t getuid (void) __THROW;
/* Get the real group ID of the calling process.  */
extern __gid_t getgid (void) __THROW;
/* Return the login name of the user.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern char *getlogin (void);
#include 
/* A record in the user database.  */
struct passwd
{
  char *pw_name;		/* Username.  */
  char *pw_passwd;		/* Hashed passphrase, if shadow database
                                   not in use (see shadow.h).  */
  __uid_t pw_uid;		/* User ID.  */
  __gid_t pw_gid;		/* Group ID.  */
  char *pw_gecos;		/* Real name.  */
  char *pw_dir;			/* Home directory.  */
  char *pw_shell;		/* Shell program.  */
};

6. 主机信息

#include 
// These  system  calls  are used to access or to change the system hostname.
int gethostname(char* name, size_t namelen);
int sethostname(const char* name, size_t len);
long gethostid(void);


#include 
// get name and information about current kernel
int uname(struct utsname *name);


/* Structure describing the system and machine.  */
struct utsname
  {
    /* Name of the implementation of the operating system.  */
    char sysname[_UTSNAME_SYSNAME_LENGTH];

    /* Name of this node on the network.  */
    char nodename[_UTSNAME_NODENAME_LENGTH];

    /* Current release level of this implementation.  */
    char release[_UTSNAME_RELEASE_LENGTH];
    /* Current version level of this release.  */
    char version[_UTSNAME_VERSION_LENGTH];

    /* Name of the hardware type the system is running on.  */
    char machine[_UTSNAME_MACHINE_LENGTH];

#if _UTSNAME_DOMAIN_LENGTH - 0
    /* Name of the domain of this node on the network.  */
# ifdef __USE_GNU
    char domainname[_UTSNAME_DOMAIN_LENGTH];
# else
    char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
  };

7. 日志

/var/log/
#include 
void syslog(int priority, const  char* message, arguments...);


void closelog(void); void openlog(const char* ident, int logopt, int facility);
int setlogmask(int maskpri);


// 实测会被记录到 /var/log/syslog下:
// syslog(LOG_USER, "hi, this is log... - %m\n");
// Oct 28 09:29:34 MyComputer main: hi, this is log... - Success


pid_t getpid(void);
pid-t getppid(void); // 获得父进程pid

8. 资源和限制

#include 
int getpriority(int which, id_t who);
int getpriority(int which, id_t who, int priority);
int getrlimit(int resource, struct rlimit *r_limit);
int setrlimit(int resource, const struct rlimit *r_limit);
int getrusage(int who, struct rusage* r_usage);

一个程序耗费的CPU时间 = 用户时间(程序执行自身指令所耗费的时间)+系统时间(操作系统为执行程序所耗费的时间,即输入输出、系统调用时间)。

// 确定当前进程优先级
priority = getpriority(PRIO_PROCESS, getpid());

ulimit -a
# 查看操作系统对软件的资源限制

五、终端

检查文件描述符是否连接到一个终端

#include 
int isatty(int fd);

输出缓冲是由libc实现的,那么输入缓冲应该也是由libc实现的

/dev/tty

Linux提供了一个特殊设备/dev/tty(点击页内跳转) ,该设备始终执行当前终端或当前的登录会话。

终端驱动程序与其接口

Linux程序设计(上)_第11张图片
通过控制接口能够控制的主要功能:

  • 行编辑:是否允许用退格键进行编辑
  • 缓存:是立即读取字符,还是等待一段可配置的延迟之后再读取它们。
  • 回显:允许控制字符的回显,例如读取密码时
  • 回车/换行(CR/LF):定义如何在输入/输出是映射回车/换行符,比如打印\n字符时应该如何处理
  • 线速:~

硬件模型
Linux程序设计(上)_第12张图片
termios结构

#include 
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int actions, const struct* termios_p);
struct termios
  {
    tcflag_t c_iflag;		/* input mode flags */
    tcflag_t c_oflag;		/* output mode flags */
    tcflag_t c_cflag;		/* control mode flags */
    tcflag_t c_lflag;		/* local mode flags */
    cc_t c_line;			/* line discipline */
    cc_t c_cc[NCCS];		/* control characters */
    speed_t c_ispeed;		/* input speed */
    speed_t c_ospeed;		/* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
  };

输入模式

输入模式控制输入数据(终端驱动程序从串口或键盘接收到的字符)在被传递给程序之前的处理方式。
c_iflag成员宏:

  • BRKINT: 当在输入行中检测到一个终止状态(链接丢失)时,产生一个中断
  • IGNBRK: 忽略输入行中的终止状态
  • ICRNL: 将接收到的回车符转换为新行符
  • IGNCR: 忽略接收到的回车符
  • INLCR: 将接收到的新行符转换为回车符
  • IGNPAR: 忽略奇偶校验错误的字符
  • INPCK: 对接收到的字符执行奇偶校验
  • PARMRK: 对奇偶校验错误做出标记
  • ISTRIP: 将所有接收到的字符裁减为7比特
  • IXOFF: 对输入启用软件流控
  • IXON: 对输出启用软件流控

输出模式

控制输出字符的处理方式,即由程序发送出去的字符在传递到串口或屏幕之前是如何处理的。
c_oflag成员宏:

  • OPOST: 打开输出处理功能
  • ONLCR: 将输出中的换行符转换为回车/换行符
  • OCRNL: 将输出中的回车符转换为新行符
  • ONOCR: 在第0列不输出回车符
  • ONLRET: 不输出回车符
  • OFILL: 发送填充字符以提供延时
  • OFDEL: 用DEL而不是NULL字符作为填充字符
  • NLDLY: 新行符延时选择
  • CRDLY: 回车符延时选择
  • TABDLY:制表符延时选择
  • BSDLY: 退格符延时选择
  • VTDLY: 垂直制表符延时选择
  • FFDLY: 换页符延时选择

控制模式

控制中断的硬件特性
c_cflag成员的宏:

  • CLOCAL: 忽略所有调制解调器的状态行
  • CREAD: 启用字符接收器
  • CS5: 发送或接收字符时用5比较
  • CS6: 发送或接收字符时用6比较
  • CS7: 发送或接收字符时用7比较
  • CS8: 发送或接收字符时用8比较
  • CSTOPB: 每个字符使用两个停止位而不是一个
  • HUPCL: 关闭时挂断调制解调器
  • PARENB: 启用奇偶校验码的生成和检测功能
  • PARODD: 使用奇校验而不是偶校验

本地模式

控制终端的各种特性
c_lflag宏:

  • ECHO: 启用输入字符的本地回显功能
  • ECHOE: 接收到ERASE时执行退格、空格、退格的动作组合
  • ECHOK: 接收到KILL字符时执行行删除操作
  • ECHONL: 回显新行符
  • ICANON: 启用标准输入处理
  • IEXTEN: 启用基于特性实现的函数
  • ISIG: 启用信号
  • NOFLSH: 禁止清空队列
  • TOSTOP: 在试图进行写操作之前给后台进程发送一个信号

特殊字符控制

特殊字符是一些字符组合,如Ctrl+C,

-

关闭回显,修改终端速度等等

终端输出

终端类型
echo $TERM

xterm-256color

shell通过xterm-256color程序(一个X视窗系统中的终端仿真程序)或是提供类似功能的程序运行的

查看终端当前信息属性
stty -a

speed 38400 baud; rows 75; columns 274; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

输出terminfo数据项的可读版本
infocmp xterm

#include 
// 将当前终端类型设置为参数term指向的值
int setupterm(char* term, int fd, int *errret);
- -1: terminfo数据库不存在
- 0:terminfo数据库中没有匹配的数据项
- 1:成功

该函数成功返回常量OK,失败返回ERR。如果errret被设置为空指针,setupterm函数会在失败时输出一条诊断信息并导致程序直接退出
just like this:

#include 
#include 
#include 
#include 

int main()
{
    setupterm("unlisted, fileno(stdout), (int*)0);
    printf("Done.\n");
    exit(0);
}

终端控制程序示例

https://gitee.com/overcast-to-clear/linux-programming-/tree/master/%E4%BA%94%E3%80%81%E7%BB%88%E7%AB%AF%E6%8E%A7%E5%88%B6%E7%A8%8B%E5%BA%8F%E7%A4%BA%E4%BE%8B
https://gitee.com/overcast-to-clear/linux-programming-/blob/master/%E4%BA%94%E3%80%81%E7%BB%88%E7%AB%AF%E6%8E%A7%E5%88%B6%E7%A8%8B%E5%BA%8F%E7%A4%BA%E4%BE%8B/example.c

检测击键动作

MS-DOS中有kbhit函数,用于在没有实际进行读操作之前检测是否某个键被按过,而Linux中没有次函数。

虚拟控制台

特殊文件“/dev/tty”总是指向当前终端

Linux提供了虚拟控制台的功能,一组终端设备共享PC电脑的屏幕、键盘和鼠标。通常,一个Linux安装将配置多个虚拟控制台。虚拟控制台通过字符设备文件/dev/ttyN使用

使用命令who 和ps,即可看到目前登录进系统的用户,以及在这个虚拟控制台上运行的shell和执行的程序。

sdf@MyComputer:/dev$ who
sdf      tty2         2023-10-28 08:20 (tty2)
sdf      tty3         2023-10-28 21:31

sdf@MyComputer:/dev$ ps -e | grep tty
   2355 tty2     00:00:00 gdm-wayland-ses
   2361 tty2     00:00:00 gnome-session-b
  22039 tty3     00:00:00 login
  22122 tty3     00:00:00 bash

可以通过Ctrl+Alt_F在不同的ttyN之间切换,字符界面可以通过Alt+F在不同的虚拟控制台之间进行切换。

可以同时在Linux系统上运行多个X视窗会话:

startx -- :1

/usr/lib/xorg/Xorg.wrap: Only console users are allowed to run the X server

在其他方面,虚拟控制台的行为都与普通硬件终端一样。

伪终端

PTY

伪终端的行为与虚拟控制台的行为非常相似,唯一的区别是伪终端没有对应的硬件设备。它们可以用来为其他程序提供终端形式的接口。

AI

chatgpt 3.5

  1. 伪终端(Pseudo Terminal,PTY):

    • 伪终端是一种虚拟设备,用于模拟物理终端的功能。
    • 它是一个用于进程之间的通信的接口,使得一个进程可以像在终端上一样与另一个进程交互。
    • 伪终端通常以一对的方式出现,一个用于接收用户输入,另一个用于输出给应用程序。
    • 伪终端用于在不同进程之间建立交互式会话,例如SSH会话或在终端窗口中运行shell或其他终端应用程序。
  2. 虚拟终端(Virtual Terminal):

    • 虚拟终端是一个概念,它不对应实际的硬件设备,而是指在Linux系统上提供的多个虚拟控制台终端。
    • 这些虚拟终端允许用户在同一台计算机上同时运行多个命令行会话或终端应用程序。
    • 用户可以通过切换虚拟终端来管理多个命令行界面,每个虚拟终端通常对应一个文本控制台。
    • 虚拟终端通常通过键盘快捷键(例如Ctrl+Alt+F1到F6)进行切换。
  3. 区别:

    • 伪终端是一种虚拟设备,用于在不同进程之间建立交互式会话,而虚拟终端是在同一台计算机上管理多个命令行会话的虚拟控制台。
    • 伪终端通常用于进程之间的通信,例如通过SSH建立远程会话或在终端窗口中运行终端应用程序,而虚拟终端用于在同一台计算机上管理多个本地会话。
    • 伪终端是一个设备接口,虚拟终端是一个用户界面的概念,不对应硬件设备

Bing chat
在Linux系统中,伪终端(也被称为pty)是一对字符设备,包括伪终端主设备(master)和伪终端从设备(slave)。其中,slave对应/dev/pts/目录下的一个文件,而master则在内存中标识为一个文件描述符12。

伪终端的主要作用是模拟物理终端的行为。它由运行在用户态的终端模拟器提供。Master端更接近用户显示器、键盘的一端,而slave端则运行在虚拟终端上的命令行接口(CLI)程序。Linux的伪终端驱动程序会把master端(如键盘)写入的数据转发给slave端供程序输入,同时把程序写入slave端的数据转发给master端供显示器驱动等读取12。

虚拟终端是Linux系统中的一个概念,它是通过软件在物理机上虚拟出来的终端3。Linux默认存在6个虚拟终端,可以使用快捷键(Ctrl + Alt + F1~F6)切换3。这些虚拟终端使用tty(teletypewriter)显示3。虚拟终端并不直接对应硬件设备,但它们模拟了物理设备(如键盘和显示器)的行为13。

总的来说,伪终端和虚拟终端都是Linux系统中用于提供用户交互界面的重要工具。它们使得用户可以在同一时间运行多个会话,并且可以方便地在这些会话之间切换

六、curses函数库管理基于文本的屏幕

curses库简介

上一章介绍了如何加强对字符输入的控制,以及如何以终端无关的方式提供字符输出。使用通用终端接口(GTI或termios)和通过tparm以及相关函数控制escape转义系列都会导致大量的底层代码。
而curses库函数提供了高层接口,它位于简单的文本行程序和完全图形化界面的X视窗系统程序(如GTK+/GNOME和qt/KED)之间。

stdscr与终端屏幕的尺寸完全一样。窗口可以互相重叠,可以拥有自己的多个子窗口,但每个子窗口必须总是被包含在它的父窗口内。

stdscr数据结构对应的是“标准屏幕”,与stdout非常相似,是个缓存;curscr数据结构和stdscr相似,但它对应的是当前屏幕的样子。在程序调用refresh之前,输出到stdscr上的内容不会显示在屏幕上。curses函数库会在refresh函数被调用时比较stdscr与curscr(屏幕当前的样子)之间的不同之处,然后用这两个数据结构之间的差异来刷新屏幕。

移动和更新窗口

int touchwin(WINDOW *window_ptr);
int scrollok(WINDOW *window_ptr, bool scroll_flag);
int scroll(WINDOW *window_ptr);

touchwin非常特殊,他的作用是通知curses函数库其指针指向的窗口内容已经发生改变,在下次调用wrefresh函数是,curses必须重新绘制该窗口,即使用户实际并未修改其内容。当屏幕上重叠着多个窗口时,可以用过该函数来安排要显示的窗口。

优化屏幕刷新
目的:尽量减少在屏幕上绘制的字符数目

#include 
int wnoutrefresh(WINDOW* window_ptr);
int doupdate(void);

Linux程序设计(上)_第13张图片
子窗口

#include 
WINDOW* subwin(WINDOW*parent, int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW* win_to_del);

Linux程序设计(上)_第14张图片
pad
可以控制尺寸大于正常窗口的逻辑屏幕。

#include 
WINDOW *newpad(int num_of_lines, int num_of_cols);
// 将pad从坐标(pad_row, pad_col)开始的区域写到屏幕上指定的显示区域,该显示区域
// 从(scr_row_min,scr_col_min)到(scr_row_max,scr_col_max)
int prefresh(
    WINDOW *pad_ptr, 
    int pad_row, 
    int pad_col, 
    int scr_row_min, 
    int scr_col_min, 
    int scr_row_max, 
    int scr_col_max
);
pnoutrefresh;类似wnoutrefresh

七、数据管理

内存管理

当物理内存耗尽时,内核便会使用交换空间(swap space),在Linux中,交换空间是一个在安装系统是分配的独立的磁盘区域。

文件锁

open系统调用并且带上O_CREAT和O_EXCL标志,这样能够以一个原子操作同时完成两项工作:确定文件不存在,然后创建它。

#incldue <unistd.h>
#include 
#include 
#include 
#include 
const char* lock_file = "/tmp/LCK.test2";
int main(){
    int fd;
    int tries = 10;
    while(tries--){
		fd = open(lock_file, O_RDWR|O_CREAT|O_EXCL, 0444);
		if (fd == -1) {
			printf("%d -lock already present\n", getpid());
			sleep(3);
		} else {
			printf("%d - i have exclusive access\n", getpid());
			sleep(1);
			close(fd);
			unlink(lock_file);
		}
	}
	return 0;
}

区域锁定:

#include 
int fcntl(int fd, int command, ...); 
  • F_GETLK
  • F_SETLK
  • F_SETLKW
int fcntl(int fd, int command, struct flock *flock_st);

flock(文件锁)结构依赖具体的实现,但它至少包含下属成员:

  • short l_type
  • 定义了文件中的一个区域
    • short l_whence
      • SEEK_SET(文件头),SEEK_CUR(当前位置),SEEK_END(文件尾)
    • off_t l_start
      • 相对于l_whence的起始位置
    • off_t l_len
      • 区域长度
  • pid_t l_pid

文件中的每个字节在任意时刻只能拥有易总类型的锁:共享锁、独占锁、解锁

command参数:

  1. F_GETLK
  2. F_SETLK
  3. F_SETLKW

在使用文件锁时应该使用系统调用read和write读写,而不是用libc读写。

文件锁测试 P246

dbm数据库


八、MySQL

十五、套接字

  • 套接字工作原理
  • 套接字的属性、地址和通讯
  • 网络信息和互联网守护进程
  • 客户和服务器

socket是syscall

套接字是一种通讯机制,凭借这种机制,客户/服务器系统的开发工作即可以在本地单机上进行,也可以跨网络进行。Linux所提供的功能(如打印、连接数据库和提供web页面)和网络工具(如用于远程登录的rlogin和用于文件传输的ftp)通常都是通过套接字来进行通信的。

套接字程序是如何通过套接字来维持一个连接的:
Linux程序设计(上)_第15张图片
代码地址:

https://gitee.com/overcast-to-clear/linux-programming-.git

用完一个套接字后,就应该把它删除,即使在程序因接收到一个信号而异常终止的情况下也应该这么做。

client:

1. 创建socket
2. 根据socket以及addr建立connect
3. 读写socket文件描述符
4. close

server

1. 创建socket
2. 指定地址
3. 将地址与sfd 绑定(bind)
4. 监听该sfd
5. 调用accept等待客户端链接
6. 读写客户端对应的文件描述符
7. close

1. 套接字属性

https://blog.csdn.net/surfaceyan/article/details/125341896

套接字特性由3个属性确定:域(domain,又称协议族,protocal family),类型(type)和协议(protocal)。

extern int socket (int domain, int type, int protocol);
参数:
	- domain: AF_INET, ipv4
              AF_INET6, ipv6
              AF_UNIX, unix文件系统域
    - type:  - 流套接字,SOCK_STREAM, TCP/IP
             - 数据报套接字, SOCK_DGRAM, UPD/IP
    - protocal: 填0,默认即可

X/Open规范中在同文件netdb.h中定义了一个常量IPPORT_RESERVED代表保留端口号的最大值。

2. 套接字地址

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket.  */
struct sockaddr_un
  {
    sa_family_t sun_family;    // AF_UNIX
    char        sun_path[108];		/* Path name.  */
  };


/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */
    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };
/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };



/* Structure describing a generic socket address.  */
struct sockaddr
  {
    sa_family_t sa_family;	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
  };

3. 套接字命名

要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名。AF_INET套接字就会关联到一个IP端口号。

#include 
int bind(int socket, const struct sockaddr* address, size_t address_len);

4. 创建套接字队列

为了能够在套接字上接收进入的链接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成该工作。

#include 
int listen(int socket, int backlog);

backlog代表允许的队列中的最大数量,超过这个数量后,再往后的连接将被拒接,导致客户端连接请求失败。

5. 接受连接

通过accept系统调用来等待客户端建立对该套接字的连接

#include 
int accept(int socket, struct sockaddr*address, size_t*address_len);

accept系统调用只有当客户程序试图连接到有socket参数指定的套接字上时才返回。这里的客户指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来与该客户进行通讯,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
Linux程序设计(上)_第16张图片

int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK|flags);

在这里插入图片描述


1. 请求连接

客户端程序通过在一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。

int connect(int socket, const struct sockaddr*addr, size_t addr_len);

Linux程序设计(上)_第17张图片

2. 使用完成后应关闭sockfd

小于1024的端口号默认是为系统保留的
关于/etc/services文件:
1)作用
/etc/services文件是记录网络服务名和它们对应使用的端口号及协议。
2)格式
文件中的每一行对应一种服务,它由4个字段组成,中间用TAB或空格分隔,分别表示“服务名称”、“使用端口”、“协议名称”以及“别名”。

服务名"tab"    端口号/协议名    “tab” 别名
kermit        1649/udp
          1701/tcp    	 l2f
          1701/udp       l2f
h323gatedisc  1718/tcp
http          80/tcp          www             # WorldWideWeb HTTP
https           443/tcp                         # http protocol over TLS/SSL
https           443/udp                         # HTTP/3

回路(loopbackq)网络只包含一台计算机,传统上它被称为localhost,标准IP: 127.0.0.1即为本地主机。可在网络主机文件/etc/hosts找到

用netstat命令可查看网络连接状况 netstat -A inet

网络信息

因特网守护进程xinetd/inetd

其配置文件。。。

套接字选项

setsockopt: https://blog.csdn.net/surfaceyan/article/details/125341896

select系统调用

https://blog.csdn.net/surfaceyan/article/details/125341896
select example

多客户服务器程序

通过selelct调用避免使用子进程
multicli_server.c

数据报(UDP)

udp.c

你可能感兴趣的:(Linux,操作系统,C,linux,c++,c语言)