CS162 shell
创始人
2024-03-17 11:53:07

本文记录我在做shell这个作业时用到有关资源,如Linux系统调用、Linux基础知识、C语言知识等。

这里只是非常简略地记录了一下,并且可能有理解不正确的地方,你可以把本文当作一个索引和没有思路时的启发,详细的信息可以再去查,我也给出了一些可能会用到的文档链接。

另一方面,我不想这篇文章干扰了你的思路,如果你发现你完全没有用我说到的东西就实现了功能,那也不要怀疑自己,毕竟,shell肯定有多种实现的方法。

判分问题:我在判分的时候一直是8分,直到完成了signal handling后才变成了9分,所以不要担心分数为什么一直不变。

对于各个系统调用,推荐你看官方的Linux manual或者可以在命令行直接用man查看。

文章目录

    • support for cd and pwd
      • chdir
      • getcwd
      • errno
      • tokens
      • 函数指针cmd_fun_t
      • unused
      • getenv
    • program execution
      • C语言的 ...(ellipse)语法
      • exec
      • symbolic link
        • 创建链接
        • 删除链接
      • stat
      • fork
      • wait
      • exit vs kill
      • gdb debug在父子进程间切换
      • 在shell中启动另一个shell产生SIGTTIN报错
      • process group
      • file descriptor
      • isatty
    • Path resolution
      • access
      • strtok和strdup
    • Redirection
      • IO Redrection
      • read write
      • dup
      • open
      • close
      • linux文件类型
      • uid
    • Pipes
      • pipe
      • 优先级问题
      • 关闭不用的文件描述符
    • Signal handling
      • getpgid和getpgrp
      • setpgid和setpgrp
      • tcgetpgrp和tcsetpgrp
      • signal
      • sigaction

support for cd and pwd

chdir

#include
int chdir(const char *path);
int fchdir(int fd);

chdir()改变当前进程的工作目录到path指定的位置处。

fchdir()与前者唯一不同的是传入的参数是一个打开文件描述符open file descriptor

返回值:若成功则返回0,否则返回-1,并且errno被设置以反映错误。

通过fork()创建的子进程继承父进程当前的目录。

getcwd

#include
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);

返回一个包含绝对路径的以null结尾的字符串,该路径即当前进程的工作目录,该值通过返回值和参数buf返回(如果有)。

  1. getcwd:如果路径长度超过了size,则返回NULL,并且errno设置为ERANGE,程序应该检查该错误,若需要,可以分配一个更大的buf
  2. get_currrent_dir_name:会申请一个足够大的空间存放目录,如果环境变量PWD被设置了,并且其值正确,则返回PWD,使用者应该手动释放buf

errno

#include

表示上次错误的一个数字int,由系统调用(system call)设置,不同的数字可以表示不同的错误,其值都是正数。

tokens

struct tokens {size_t tokens_length;char** tokens;size_t buffers_length;char** buffers;
}

存放目录字符的结构体,其中包括一个缓冲区buffers

函数指针cmd_fun_t

typedef int cmd_fun_t(struct tokens* tokens);

该行代码定义了一个函数指针cmd_fun_t,其指向一个函数,参数为tokens*,返回值类型为int

unused

该关键字标识的参数可能在函数中未使用。

getenv

#include char *getenv(const char *name);

getenv("HOME")可以得到/home/username目录。

program execution

C语言的 …(ellipse)语法

函数参数最后可以使用...,这个是为了让函数可以传入可变数量的参数,下面是一个例子:

int a_function(int x, ...) {va_list list;	// 存放参数的列表va_start(list, x);	// 初始化listva_arg(list, int); // 返回list中的第一个参数,以int形式返回va_arg(list, int); // 返回list中的第二个参数,以int形式返回...					// 不断地取va_end(list);// 取完后清空list
}a_function(3, 1, 2, 3);  // 调用函数,可以传入不同数量的参数
a_function(5, 2, 9, 1, 8, 7);

要注意的是必须明确地知道每个参数的类型,才能保证取数的正确。

exec

include 
extern char **environ;int execl(const char *pathname, const char *arg, .../*, (char *) NULL */);
int execlp(const char *file, const char *arg, .../*, (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

在任务2中不使用execvp

exec族的函数将当前进程替换为pathnamefile指定的进程,其中arg, ...argv[]存放参数,例如,对于ls来说,-a-l就是其参数,要注意的是,argv[0]需要放路径,如对于执行ls,可以这样写:

char ** args = { "/bin/ls", "-a", "-s", NULL };
execl(args[0], args[0], args[1], args[2], args[3]);
// 或
execv(args[0], args);

symbolic link

我认为可以将symbolic link当作引用理解。

创建链接

linux中可以创建链接,每个链接指向一个地址,对该链接的修改等同于对原地址内容的修改。

ln -s /home/transactions.txt school/trans.txt

上面的命令创建了一个链接school/trans.txt,对trans.txt的修改即对transactions.txt的修改。

要注意的是链接所在的文件夹(本例中为school)在创建连接前必须已经创建,否则报错。

也可以对文件夹创建链接:

ln -s /home/junhao junhao

链接中会包含所有原文件夹的内容,对链接中文件的修改也会反映到原文件夹。

删除链接

首先可以使用如下命令检查某个文件是否为链接,如果为链接,可以看到有xxx -> xxx形式的输出。

ls -l pathname

例子如下:

可以使用如下命令删除链接:

unlink linkname

stat

#include int stat(const char *restrict pathname,struct stat *restrict statbuf);

该函数返回文件pathname的相关信息,存放在statbuf中,

fork

#include pid_t fork(void);

该函数用于创建子进程,如果创建子进程成功,则函数在子进程中返回0,在父进程中返回子进程的pid,如果失败则在父进程中返回-1,并且errno被设置。

wait

#include pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
/* This is the glibc and POSIX interface; seeNOTES for information on the raw system call. */

wait族的函数用于等待子进程状态的改变,并且获取其信息,状态改变有以下几种情况:

  1. 子进程终止
  2. 子进程暂停
  3. 子进程恢复运行

wait()将父进程挂起,直到其子进程之一终止,其中wait(&wstatus)waitpid(-1, &wstatus, 0)作用一致。

waitpid()将父进程挂起,直到由pid指定的子进程状态改变,默认地,该方法等待子进程终止,可以通过设置参数options指定子进程的行为。

exit vs kill

exit是进程自己结束,而kill是进程关闭其他进程。

gdb debug在父子进程间切换

为了对子进程进行debug,需要在gdb中输入以下命令:

(gdb) set follow-fork-mode child

输入如下命令切换回父进程:

(gdb) set follow-fork-mode parent

在shell中启动另一个shell产生SIGTTIN报错

这个问题在后面的Signal Handling部分中会被解决,目前可以不管。

process group

若干个进程组成一组,指向该组的信号量可以统一控制该组中的所有进程。每个组有一个id,值与创建该组的进程的id一致。

fork出来的子进程与父进程在同一组中。

file descriptor

文件描述符是一个数字,为一个已打开文件的唯一标识。

isatty

#include int isatty(int fd);

Path resolution

access

#include int access(const char *pathname, int mode);

检查当前结成是否可以访问pathname文件,mode中设置检查的方式,其值可以为:

  1. F_OK:检查文件是否存在。
  2. 由R_OK、W_OK和X_OK中的若干个按或(OR)运算得到的掩码:检查文件是否存在,并且赋予读(R_OK)、写(W_OK)和执行权限(X_OK)。

返回值:若成功(文件存在,授予权限成功),则返回0,若失败,返回-1。

strtok和strdup

在解析$PATH的时候我用了strtok()进行字符串,结果这个东西直接把我的$PATH给改了。。。,为了不让strtok()修改$PATH,把用getenv()得到的$PATH用strdup进行复制,用复制品进行解析。

除此之外,还要注意strtok()不会申请新的空间,因此不需要free,珍爱生命,远离strtok()

这个strdup()strcpy()差不多,都是复制,不同的是,它可以自动malloc()一段内存,不用自己申请了,但是依然要手动free

Redirection

IO Redrection

输入输出重定向的介绍和例子

实现思路

read write

#include 
ssize_t read(int fd, void *buf, size_t count);

从文件描述符fd指向的文件中读出count字节的数据,放入缓冲buf中。如果成功,则返回读取的字节数,并且文件中的位置向前那么多。若失败,则返回-1。

#include 
ssize_t write(int fd, const void *buf, size_t count);

write与read类似。

dup

#include int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup()创建一个新的文件描述符,其指向的文件与oldfd相同,新的文件描述符的值是未被使用的值中的最小值。

dup2()的功能与dup()相同,区别是令描述符newfd指向oldfd指向的那个文件。

若成功则返回新描述符的值,失败则返回-1

open

#include int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

open()打开文件pathname,若不存在,则会选择性地进行创建文件(若flags中有O_CREAT则创建),flags由若干个标识符的按位或运算组成,如O_CERAT | O_RDONLY

如果flags中没有O_CREATO_TMPFILE,那么mode会被忽略,否则,其必须存在,

若成功,返回文件描述符,若失败,返回-1。

除此之外,你可能会用到creat(pathname, mode),并看到mode值是0600,这个数为0400 | 0200的结果,即创建文件的用户拥有对该文件的读写权限。

close

#include int close(int fd);

关闭一个文件描述符,让它不再指向任何文件且不再被使用。若成功,返回0,若失败,返回1。

linux文件类型

有如下3类文件:

uid

即user identifier,linux上的每个用户都有一个唯一的标识符。使用getuid()可以查看当前进程的uid。

uid的介绍。

Pipes

实现思路。

pipe

#include 
int pipe(int pipefd[2]);

创建一个数据单向流通的管道,用于进程间的交互。pipefd数组存放返回的2个文件描述符,pipefd[0]指向最后读取的文件,而pipefd[1]指向最后被写入的文件。

若成功,则返回0,若失败,则返回1。

优先级问题

这里有一个redirectionpipes的优先级问题,这可能是个有用的链接:pipe-redirection-precedence。下图也大致说明了二者的优先级。

关闭不用的文件描述符

这个非常重要,如果不关闭,子进程可能陷入一直等待输入的状态。如果你发现子进程无法终止,很可能是这个问题。

具体地,你可以看:

  1. 香港中文大学的小实验的2.2节
  2. CS 162的Section 3的答案
  3. 前面实现思路的链接中也有说明这个问题。

Signal handling

老师作业文档里的那个tutorial链接很有用,值得一看,这里就不重复给链接了。

getpgid和getpgrp

#include 
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);

返回id为pid的进程所在的组的group id,如果pid为0,则返回调用该方法的进程所在组的group id,若失败,则返回-1。

getpgrp()getpgid(0)作用相同。

setpgid和setpgrp

#include 
int setpgid(pid_t pid, pid_t pgid);
pid_t sepgrp(void);

将id为pid的进程所属的组切换为pgid,同理,若pid为0,则调用该方法的进程的组切换为pgid

setpgrp()setpgid(0, 0)作用相同。

tcgetpgrp和tcsetpgrp

#include 
pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd, pid_t pgrp);

tcgetpgrp()返回前台进程组的group id。

tcsetpgrp()将前台进程组设为pgrp那组,若fd为0,则用以控制标准输出。

对于以上内容,可以查看手册和下图:

signal

每个信号(signal)都有一个当前的配置(disposition),其决定了进程接收到该信号时所执行的行为,具体可看文档。

sigaction

#include 
int sigaction(int signum, const struct sigaction *restrict act,struct sigaction *restrict oldact);

该函数修改进程收到指定的某个信号(signal)时所执行的行为。其中:

  1. signum为指定的信号,可以是除了SIGKILLSIGSTOP外的所有有效信号。
  2. act中用于指定新的动作,不为NULL,否则无法指定新动作。
  3. oldact用于存放旧的动作,不为NULL,否则无法存放旧动作。

其中struct sigaction如下所示:

struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);
};

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...