一起学习unix环境高级编程--unix基础知识(1)

本文介绍了UNIX系统中的输入输出操作、进程控制、信号处理等基础知识,包括文件描述符的使用、进程ID的概念、线程的理解及出错处理的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先感谢stevens等呕心沥血写出来的神书。谢谢,膜拜大神!以下为小弟学习神书的笔记。

1.5.输入输出

1.文件描述符:一个进程打开,或创建一个文件时,内核会分配一个非负整数标记该文件。对文件的读写就可以使用该文件描述符。

ls > test.text:将输出结果重新定位至文件。注ls将结果输出到屏幕

3、不带缓冲的i/o

open/read/write/lseek等用文件描述符操作。

#include"apue.h"


#define BUFFSIZE 1024

int 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>包含很多unix系统调用函数,也包括变量STDIN_FILENO与STDOUT_FILENO,它们指定了标准输入和输出的文件描述符。

如何输出文件不存在,shell创建它,

./a.out >data ,在屏幕中输入结果,将结果重定位输出到文件,以ctrl+D结束,不是ctrl+c。

./a.out <infile >outfile,将一个文件内容输出到另外一个文件

4.标准i/o

#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);
}

效果和不带缓冲的i/o一样

区别是,标准i/o不用指定复制文件的大小:fgets函数读取完整的行,read函数读取指定的字节数。

头文件stdio.h包含了所有标准i/o函数的原型。

getc一次读取一个字符,然后putc将字符标准输出,读到最后是getc返回常量EOF,值为-1

1.6程序与进程

1.程序

程序是磁盘上的可执行文件。内核使用exec函数,将程序读入内存,并执行程序。

2.进程和进程ID

进程是正在运行的程序,也可以用任务表示。

内核为每个进程分配唯一的数字标识符,成为进程ID,为一个非负整数

#include"apue.h"

int main(void)
{
    printf("hello word from process ID %ld\n",(long)getpid());
    exit(0);
}

函数getpid返回pid_t的数据类型。因为不知道具体数值大小故将它强制转换成长整型,用以printf输出。

3.进程控制                                                                          

#include "apue.h"
#include <sys/wait.h>

int main(void)
{
    char buf[MAXLINE];
    pid_t pid;
    int   status;

    printf("%%");
    while (fgets(buf ,MAXLINE,stdin)!=NULL)
           {
                if (buf[strlen(buf)-1] == '\n')
                    buf[strlen(buf)-1] ='\0';
                if ((pid =fork()) <0)
                   {
                       err_sys("fork error");
                   }
                 else if (pid ==0)
                      {
                         execlp(buf, buf, (char *)0);
                         err_ret("couldn't execute:%s", buf);
                         exit(127);
                      }
                 if((pid = waitpid(pid, &status, 0))<0)
                    err_sys("waitpid error");
                    printf("%%");
           }
           exit(0);
}

这里用到了exec函数族中的三个进程控制的函数:fork/exec/waitpid。理解了这三个函数的用法。该例子就不难了。

想要输入%自身,需要%%的形式

fgets一次读入一行,当键入ctrl+D时(文件结束符),循环结束

strlen计算字符长度,直到遇到\0.而sizeof计算所占内存的大小。

fgets每行以换行符结束,而execlp函数的参数要求以\0结束。故用0代替

fork函数,创建1个和父进程一模一样的子进程,通过进程号判断父进程/子进程、父进程中返回子进程的进程号,子进程中返回进程号为0.

创建不成功返回负值

子进程中用execlp函数执行新的程序文件,execlp第一个参数,即为输入的命令(会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件),第二个参数为argv[0]...第三个参数是字符型的空指针。命令执行成功则后面的命令不执行,否则继续执行后面的。

父程序中通过调用waitpid等待子程序的结束,其返回子进程的终止状态(status变量),第一个参数为要等待进程的进程号

4 线程和线程ID

多线程能够充分利用多处理器系统的并行能力

一个进程内所有的线程共享同一地址空间/文件描述符/栈/进程相关的属性。因此各线程访问共同的数据时需要采取同步措施避免不一致

线程也有ID的,但是它只能在所属的进程中起作用。对线程的处理时,需要使用线程ID使用

1.7 出错处理

unix系统函数出错时,通常会返回一个负值(整型变量erron),也有时会返回指向对象的指针,如返回一个NULL指针

对于返回值erron:第一:没有出错,其值不会被清除。第二返回值error不为0

c标准中定义2个函数,用于打印出错信息

char*strerror(int errnum);

将errnum,输出出错的字符串,返回字符串的指针

void perror(const char×msg);

输出msg指向的字符串: 对应error值的出错信息,换行符

#include "apue.h"
#include <errno.h>
int main (int argc, char *argv[])
{
fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
errno = ENOENT;
perror(argv[0]);
exit(0);
}

stderr向屏幕输出


出错恢复:在error.h中分为致命性与非致命性。对于致命性的错误,无法执行恢复动作,最多在屏幕上打印一条出错信息。然后退出。

对于非致命性的错误,出错是暂时的。

用户表示

1 用户ID

用户ID在口令登录文件中是一个数值,用来表示不同的用户。用户不能更改自己的ID。ID为0的为根用户,或超级用户,登录名为root

mac os x客户端是不能打开超级用户的,仅能在服务器版本使用。

2 组ID

组ID也是由系统管理员在分配用户名时,分配的。多个用户ID具有相同的组ID。

同组中不同的成员允许共享资源。组文件会将组名映射为组ID。

检验用户权限时,字符串之间的比较比整型数更耗时间,但是对用户来说,使用名字更方便,所以口令文件包含登陆名与用户ID的映射关系。

#include "apue.h"

int main(void)
{
printf("uid = %d, gid = %d\n",getuid(), getgid());
exit(0);
}
getuid与getgid分别获取组ID与用户ID

3.附属组ID

1个用户可以属于多个附属组

1.9信号

信号用于通知进程发生了某种情况。

进程有3种处理信号的方式:

1.忽略信号:

2.按系统默认方式处理:终止进程

3.捕捉该信号:键盘上可以产生2种信号:中断键(ctrl+c或delete)退出键(ctrl+\),用于中断进程

第二种是:调用kill函数:在一个进程中调用此函数,可以向另一进程发送一个信号。使用这种方法只能以该进程拥有者或超级用户的方式。

捕捉信号调用signal函数。

#include "apue.h"
#include <sys/wait.h>

static void sig_int(int);

int main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;

if (signal(SIGINT, sig_int) == SIG_ERR)
   err_sys("signal error");

printf("%% ");
while (fgets(buf, MAXLINE, stdin) !=NULL)
      {
           if (buf[strlen(buf)-1] =='\n')
               buf[strlen(buf) -1]=0;

           if ((pid = fork()) <0)
              {
                  err_sys("fork error");
              }
            else if (pid == 0)
                 {
                    execlp(buf, buf, (char*)0);
                    err_ret("couldn't execute:%s",buf);
                    exit(127);
                 }

            if ((pid = waitpid(pid, &status ,0)) <0)
               err_sys("waitpid error");
            printf("%% ");
      }
      exit(0);
}
void sig_int(int signo)
{
printf("interrupt:%% ");
}

1.10时间值

unix系统使用2种不同的时间

日历时间:1970年1月1日 00:00:00以这个时间为基准。数据类型time_t用于保存时间值

进程时间:也成cpu时间,用于度量进程使用cpu资源的时间。进程时间以时钟滴答计算。每秒钟去50/60/100个时钟滴答

数据类型clock_t保存这种时间值。

衡量进程的执行时间的量:

始终时间:进程运行的时间总量,与系统同时运行的进程数有关

用户cpu时间:执行用户指令所用的时间量。

系统cpu时间:执行内核程序所需时间。用户cpu时间与系统cpu时间之和为cpu时间。

time+命令可以查看执行命令的时间

1.11系统调用和库函数

操作系统提供多种服务的入口点,这些入口点称为系统调用。unix为每个系统调用在标准c库中设置一个相同的名字,

用户用标准c调用这些函数。通用的库函数也可能调用内核函数。例如malloc调用sbrk系统调用实现自己的分配空间算法。

库函数与内核函数的职责不同,内核中的系统调用分配一块空间给进程,库函数malloc在用户层次管理这个空间。

unix只提供一个系统调用,返回自世界时1970年1月1日零时起所经过的描述。将其转换成适合本时区的时间,留给用户进程处理。

系统调用通常提供一个最小接口,库函数提供教复杂的功能


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值