[1 Unix基础]

2024-08-21 16:38
文章标签 基础 unix

本文主要是介绍[1 Unix基础],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.2 Unix体系架构

内核的接口被称为系统调用(system call)。公用函数库构建在系统调用接口之上,应用程序即可使用公用函数库,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。

图1-1 Unix操作系统的体系结构

1.3 登录

系统在口令文件(/etc/passwd)中查看登录名。口令文件的登录项由7个以冒号分隔的字段组成,依次是:登录名,加密口令,数字用户ID(205),数字组ID(105),注释字段,起始目录(/home/sar)以及shell程序(/bin/ksh)。

sar:x:205:105:Stephen:/home/sar:/bin/ksh

系统已经将加密口令(x)移动到了另一个文件中,第6章将说明这种文件以及访问它们的函数。

1.4 文件和目录

1 文件系统

stat和fstat函数返回包含所有文件属性的一个信息结构,第4章将详细说明。

2 文件名

只有斜线(/)和空字符不能出现在文件名中。斜线用来分隔构成路径名的各文件名,空字符则用来终止一个文件名。

3 目录

列出一个目录中所有文件的名字,ls(1):1表示后面接1个参数

#include "apue.h"
#include <dirent.h>int main(int argc, char *argv[])
{DIR				*dp;struct dirent	*dirp;if (argc != 2)err_quit("usage: ls directory_name");if ((dp = opendir(argv[1])) == NULL)err_sys("can't open %s", argv[1]);while ((dirp = readdir(dp)) != NULL)printf("%s\n", dirp->d_name);closedir(dp);exit(0);
}

dirent.h,使用opendir和readdir函数原型,以及dirent结构的定义。

exit,0表示正常结束,参数值1~255表示出错。

1.5 输入和输出

1 文件描述符

文件描述符通常是小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符。

2 标准输入、标准输出和标准错误

每当运行一个程序,shell为其打开3个文件描述符,即标准输入、标准输出和标准错误。默认,这3个描述符都链接向终端。shell可以使这3个描述符重新定向到某个文件:

ls > file

标准输出重定向到file文件。

3 不带缓冲的I/O

函数open、read、write、lseek以及close提供了不带缓冲的I/O。

从标准输入读,并向标准输出写。程序用于复制任一Unix文件,类似cat。

#include "apue.h"#define	BUFFSIZE	4096int main(void)
{int		n;char	buf[BUFFSIZE];while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)if (write(STDOUT_FILENO, buf, n) != n)err_sys("write error");if (n < 0)err_sys("read error");exit(0);
}

头文件<unistd.h>包含read和write函数声明,STDIN_FILENO(0),STDOUT_FILENO(1)。

read函数返回读取的字节数。当到达输入文件的末尾时,read返回0,程序停止运行。如果发生了一个读错误,read返回-1。

./mycat > data

标准输入是终端,标准输出重定向到文件data,标准错误是终端。该程序将用户输入的各行复制到标准输出,输入文件结束符(ctrl+d)后终止。
 

./mycat < infile > outfile

标准输入重定向到infile,标准输出重定向到outfile。该程序会将infile文件的内容复制到outfile文件中。

4 标准I/O

标准I/O为那些不带缓冲的I/O提供了带缓冲的接口。使用标准I/O无需考虑如何选取最佳的缓冲区大小,如3中的BUFFSIZE;同时简化了对输入行的处理,例如,fgets函数读取一行而read函数读取指定字节数。

将标准输入复制到标准输出,也能复制任一Unix文件:

#include "apue.h"int main(void)
{int		c;while ((c = getc(stdin)) != EOF)if (putc(c, stdout) == EOF)err_sys("output error");if (ferror(stdin))err_sys("input error");exit(0);
}

getc一次读取一个字符,putc将此字符写到标准输出。读到输入的最后一个字节时,getc返回常量EOF。<stdio.h>中包含stdin,stdout和EOF(通常为-1)的定义。

1.6 程序和进程

内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。

3 进程控制

有3个用于进程控制功能的主要函数:fork、exec和waitpid。

该程序从标准输入读取命令,并执行命令。类似shell。

#include "apue.h"
#include <sys/wait.h>int main(void)
{char	buf[MAXLINE];	/* from apue.h */pid_t	pid;int		status;printf("%% ");	/* print prompt (printf requires %% to print %) */while (fgets(buf, MAXLINE, stdin) != NULL) {if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = 0; /* replace newline with null */printf("parent getpid:%ld \n", (long)getpid());if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {		/* child */printf("child pid:%d \n", pid);printf("child getpid:%ld \n", (long)getpid());execlp(buf, buf, (char *)0);// todoerr_ret("couldn't execute: %s getpid:%ld", buf, getpid());exit(127);}printf("parent wait for pid:%d \n", pid);/* parent */if ((pid = waitpid(pid, &status, 0)) < 0)err_sys("waitpid error");printf("%% ");}exit(0);
}

子进程正常退出后todo后两句没有运行,可能的解释:

1 正常情况,子进程execlp里exit(0)会退出进程。

2 异常情况,子进程没有exit(0),需要用户exit(>0)表示异常退出。

解释如上程序:

1 用标准I/O函数fgets从标准输入一次读取一行。当输入文件结束符(ctrl+d)时,fgets返回null,循环停止。

2 fgets返回的每一行是以换行符终止,需要用null替换。这是因为execlp函数要求的参数是以null结束的而不是以换行符结束的。

int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

其中第一个参数file指向可执行文件名称,因为execlp()函数会从系统PATH路径中寻找相应命令,所以不需要带完整路径;第二个参数之后就都是传给可执行文件的参数,类似main函数的args[],只是最后一个参数必须为空字符指针。

3 fork创建一个新进程,被调用一次(被父进程),返回两次(在父进程和子进程中)。

4 在子进程中,调用execlp以执行从标准输入读入的命令。这就用新的程序文件替换了子进程原来执行的程序文件。

5 父进程通过waitpid来等待子进程终止,参数指定要等待的进程(pid参数是子进程pid)。

6 该程序的限制是不能想执行的命令传递参数。

1.7 出错处理

Unix系统函数出错时,通常会返回一个负值,且整形变量errno通常被设置为具有特定信息的值。例如,open函数如果成功返回一个非负文件描述符,如果错误返回-1。open出错时,有大约15种errno值(文件不存在,权限问题等)。

linux错误码:

linux错误码大全_zhangwentaohh的专栏-CSDN博客_linux错误码

errno应注意两条规则。

1 如果没有出错,其值不会被清除。因此,仅当函数的返回值指明出错时,才检验其值。
2 任何函数不会将errno设置为0,且<errno.h>中定义的错误码都不为0。(一般>0)

C标准定义了两个函数,用于打印出错信息:

1 strerror

#include <string.h>// 返回值:指向消息字符串的指针
char *strerror(int errnum);

strerror函数将errnum映射为一个出错消息字符串。

todo:返回的char*堆内存何时free???贴下strerror源码:

/*** 
*char *strerror(errnum) - Map error number to error message string. 
* 
*Purpose: 
*       The strerror runtime takes an error number for input and 
*       returns the corresponding error message string.  This routine 
*       conforms to the ANSI standard interface. 
* 
*Entry: 
*       int errnum - Integer error number (corresponding to an errno value). 
* 
*Exit: 
*       char * - Strerror returns a pointer to the error message string. 
*       This string is internal to the strerror routine (i.e., not supplied 
*       by the user). 
* 
*Exceptions: 
*       None. 
* 
*******************************************************************************/  #ifdef _UNICODE  
wchar_t * cdecl _wcserror(  
#else  /* _UNICODE */  
char * __cdecl strerror (  
#endif  /* _UNICODE */  int errnum  )  
{  _TCHAR *errmsg;  _ptiddata ptd = _getptd_noexit();  if (!ptd)  return _T("Visual C++ CRT: Not enough memory to complete call to strerror.");  if ( (ptd->_terrmsg == NULL) && ((ptd->_terrmsg =  _calloc_crt(_ERRMSGLEN_, sizeof(_TCHAR)))  == NULL) )  return _T("Visual C++ CRT: Not enough memory to complete call to strerror.");  else  errmsg = ptd->_terrmsg;  #ifdef _UNICODE  _ERRCHECK(mbstowcs_s(NULL, errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum), _ERRMSGLEN_ - 1));  
#else  /* _UNICODE */  _ERRCHECK(strcpy_s(errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum)));  
#endif  /* _UNICODE */  return(errmsg);  
}  

2 perror

#include <stdio.h>void perror(const char *msg);

perror先输出msg指向的字符串,再基于errno的当前值在标准错误上输出一条出错信息。

说明两个出错函数的使用方法:

#include "apue.h"
#include <errno.h>int main(int argc, char *argv[])
{fprintf(stderr, "EACCES: %s\n", strerror(EACCES));errno = ENOENT;/* errno = EACCES; */perror(argv[0]);exit(0);
}测试结果:
EACCES: Permission denied
./testerror: No such file or directory

注意:将程序名(argv[0])作为参数传递给perror。这是使用惯例。使用这种方法,在程序作为管道时,就可以知道是哪个程序产生了出错信息了:

prog1 | prog2 | prog3

1.8 用户标识

3 附属组ID

/etc/group文件可以看出某个组里包含哪些用户,参考:

linux添加用户到附属组无权访问_麻麻兔的博客-CSDN博客

Unix系统是允许一个用户属于多个组的。

1.9 信号

信号用于通知进程发生了某种情况。例如,若某一进程执行除法操作,若除数为0则将名为SIGFPE(浮点异常)的信号发送给该进程。进程有以下3种处理信号的方式:

1 忽略信号。

2 按照系统默认方式处理。如除数0,系统默认方式是终止进程。

3 提供一个函数,信号发生时调用该函数,即捕捉信号。

产生信号:

1 中断键ctrl+c,对应信号SIGINT,系统默认动作是终止进程。

2 退出键ctrl+\

3 kill函数,在一个进程中调用此函数可以向另一个进程发送信号。

如下程序,为了能捕捉信号,程序调用signal函数,其中指定了当产生SIGINT信号时要调用的函数的名字sig_int。

#include "apue.h"
#include <sys/wait.h>static void	sig_int(int);		/* our signal-catching function */int main(void)
{char	buf[MAXLINE];	/* from apue.h */pid_t	pid;int		status;if (signal(SIGINT, sig_int) == SIG_ERR)err_sys("signal error");printf("%% ");	/* print prompt (printf requires %% to print %) */while (fgets(buf, MAXLINE, stdin) != NULL) {if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = 0; /* replace newline with null */if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {		/* child */execlp(buf, buf, (char *)0);err_ret("couldn't execute: %s", buf);exit(127);}/* parent */if ((pid = waitpid(pid, &status, 0)) < 0)err_sys("waitpid error");printf("%% ");}exit(0);
}void sig_int(int signo)
{// 有问题printf("interrupt\n%% ");
}

注意:信号处理函数里应该只能调用可重入函数。printf是不可重入函数,不应这么写,这里只是个demo。

可重入函数:可以在它还没有返回就再次被调用的函数。

不可重入函数:不可以在它还没有返回就再次被调用的函数。

1.10 时间值

当度量一个进程的执行时间时,Unix系统为一个进程维护了3个进程时间:

1 时钟时间:是进程运行的时间总量

2 用户CPU时间:执行用户指令所用的时间

3 系统CPU时间:为该程序执行内核程序的时间

例如,测试top命令的进程时间:

time -p top测试结果:
real 4.39
user 0.00
sys  0.01

1.11 系统调用和库函数

Unix实现提供了直接进入内核的入口点,这些入口点被称为系统调用(system call)。

《Unix程序员手册》的第2部分说明了系统调用接口,第3部分说明了通用库函数。

下图显示了应用程序、malloc函数和sbrk系统调用之间的关系:

可以看到,sbrk系统调用分配了一块内存给进程,并将虚拟地址空间映射到内存供malloc使用,而库函数malloc则在用户层次管理这一内存空间。

系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。如:

1 sbrk与malloc

2 不带缓冲的I/O与标准I/O

3 进程控制系统调用(fork、exec和wait)与system、popen

这篇关于[1 Unix基础]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093703

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

c++基础版

c++基础版 Windows环境搭建第一个C++程序c++程序运行原理注释常亮字面常亮符号常亮 变量数据类型整型实型常量类型确定char类型字符串布尔类型 控制台输入随机数产生枚举定义数组数组便利 指针基础野指针空指针指针运算动态内存分配 结构体结构体默认值结构体数组结构体指针结构体指针数组函数无返回值函数和void类型地址传递函数传递数组 引用函数引用传参返回指针的正确写法函数返回数组

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是

【MRI基础】TR 和 TE 时间概念

重复时间 (TR) 磁共振成像 (MRI) 中的 TR(重复时间,repetition time)是施加于同一切片的连续脉冲序列之间的时间间隔。具体而言,TR 是施加一个 RF(射频)脉冲与施加下一个 RF 脉冲之间的持续时间。TR 以毫秒 (ms) 为单位,主要控制后续脉冲之前的纵向弛豫程度(T1 弛豫),使其成为显著影响 MRI 中的图像对比度和信号特性的重要参数。 回声时间 (TE)

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma