1. Linux下terminal、shell、bash是什么?
  • 终端(terminal)作用是提供一个命令的输入输出环境。对于普通 Linux 来说,终端的作用是一个字符(或者模拟字符)的命令交互界面,实现对计算机的控制。
  • shell是一个命令行解释器,是linux内核的一个外壳,负责外界与linux内核的交互。shell接收命令, 然后将这些命令转化成内核能理解的语言并传给内核, 内核执行命令完成后将结果返回给用户或者应用程序。
  • 当打开一个terminal时,操作系统会将terminal和shell关联起来,当在terminal中输入命令后,shell就负责解释命令。
  1. Linux中终端的类型分类。
  • linux终端分为物理终端,伪终端,串行终端,虚拟终端。
  • 物理终端:/dev/console
  • 伪终端(远程网络终端 、图形下的终端):/dev/pts/#(数字)
  • 虚拟终端:/dev/tty#
  • 串行终端:/dev/ttys#
  • 拿WSL举例子,直接打开的黑框框页面是tty,而在VSC中连接WSL是pts。
    图片说明
    图片说明
  1. bash是什么?

bash的全称叫做Bourne Again shell,从名字上可以看出bash是Bourne shell的扩展,bash 与 Bourne shell 完全向后兼容,并且在 Bourne shell 的基础上增加和增强了很多特性,如命令补全、命令编辑和命令历史表等功能,它还包含了很多 C shell 和 Korn shell 中的优点,有灵活和强大的编程接口,同时又有很友好的用户界面。总而言之,bash是shell的一种,是增强的shell。

一个Linux系统支持很多类型的shell,cat /etc/shells可以查看。

图片说明

接下来是一些代码的测试

  1. 父进程与子进程,父进程wait阻塞,等待子进程释放自己的资源,然后父进程回收子进程的资源。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t childpid;
    int status;
    childpid = fork();
    if (-1 == childpid){
        perror("fork()");
        exit(EXIT_FAILURE);
    }
    else if(0 == childpid)
    {
        puts("In child process");
        sleep(10);
        printf("\tchild pid = %d\n", getpid());
        printf("\tchild ppid = %d\n", getppid());
        exit(EXIT_SUCCESS);
    }else{
        waitpid(childpid, &status, 0);
        puts("in parent");
        printf("\tparent pid = %d\n", getpid());
        printf("\tparent ppid = %d\n", getppid());
        printf("\tchild process exited with status %d \n", status);
    }
    exit(EXIT_SUCCESS);
}
  1. 僵尸进程

僵尸进程:子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。

僵尸进程不能被kill -9 杀掉,因为此时的僵尸进程已经不是一个完整的进程了,无法接收到信号。

如何处理掉僵尸进程?杀掉不干活的父进程,让init 1 号进程去完成子僵尸进程的释放资源的工作,init领养僵尸子进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
   pid_t id = fork();
   if(id < 0){
      perror("fork");
      return 1;
   }
   else if(id > 0){
      printf("parent [%d] is sleeping\n",getppid());
      sleep(500);
   }
   else{
      printf("child [%d] is begining\n",getpid());
      sleep(2);
      exit(1);
   }
   return 0;
}

在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。

这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态

如果这时父进程结束了,那么进程号为1的进程(例如init进程)自动会接手这个子进程,为它收尸,它还是能被清除的。

但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。

如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。在实际编程中,避免和防范僵尸进程的产生显得尤为重要。

孤儿进程:在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被进程号为1的进程(例如init进程)所收养,并由进程号为1的进程(例如init进程)对它们完成状态收集工作。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fork error:");
        exit(1);
    }
    if (pid == 0)
    {
        printf("I am the child process.\n");
        printf("pid: %d\tppid:%d\n",getpid(),getppid());
        printf("I will sleep 50 seconds.\n");
        sleep(50);
        printf("pid: %d\tppid:%d\n",getpid(),getppid());
        printf("child process is exited.\n");
    }
    else
    {
        printf("I am father process.\n");
        sleep(10);
        printf("father process is exited.\n");
    }
    return 0;
}

一些信号的知识

操作系统中,信号是事件发生时对进程的通知机制,有时也称为软件中断。

信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的准确时间。

一个进程进程可以向另一进程发送信号。

信号也是进程间通信的一种方式。

最后再来看一下守护进程的改造方式。

void init_daemon()
{
    int pid;
    int i;
    pid=fork();
    if(pid<0)
        exit(1);
    else if(pid>0)
        exit(0);
    setsid();
    pid=fork();
    if(pid>0)
        exit(0); /
    else if(pid<0)
        exit(1);
    for(i=0;i<NOFILE;i++)
        close(i);
    chdir("/tmp");
    umask(0);
    return;
}
  1. 首次调用fork:父进程直接退出,貌似要“脱胎换骨搞独立”
  2. setsid():调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。同时与控制终端脱离。
  3. 第二次调用fork:结束第一进程(组长),第二子进程继续(第二子进程不再是会话组长),目的是禁止进程重新打开控制终端。
  4. close():关闭打开的文件描述符,进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸载以及可以引起无法预料的其他错误。
  5. chdir():改变当前工作目录。脱离当前工作目录,改变到一个新的目录
  6. umask():重设文件的权限掩码,为了防止继续沿用从父进程继承下来的掩码内容。