[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

相关文章

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

安装centos8设置基础软件仓库时出错的解决方案

《安装centos8设置基础软件仓库时出错的解决方案》:本文主要介绍安装centos8设置基础软件仓库时出错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录安装Centos8设置基础软件仓库时出错版本 8版本 8.2.200android4版本 javas

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-

python操作redis基础

《python操作redis基础》Redis(RemoteDictionaryServer)是一个开源的、基于内存的键值对(Key-Value)存储系统,它通常用作数据库、缓存和消息代理,这篇文章... 目录1. Redis 简介2. 前提条件3. 安装 python Redis 客户端库4. 连接到 Re

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin

MySQL复合查询从基础到多表关联与高级技巧全解析

《MySQL复合查询从基础到多表关联与高级技巧全解析》本文主要讲解了在MySQL中的复合查询,下面是关于本文章所需要数据的建表语句,感兴趣的朋友跟随小编一起看看吧... 目录前言:1.基本查询回顾:1.1.查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J1.2.按照部门

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

mysql的基础语句和外键查询及其语句详解(推荐)

《mysql的基础语句和外键查询及其语句详解(推荐)》:本文主要介绍mysql的基础语句和外键查询及其语句详解(推荐),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录一、mysql 基础语句1. 数据库操作 创建数据库2. 表操作 创建表3. CRUD 操作二、外键