《Unix/Linux系统编程》第六周学习笔记

简易的PROC:

typedef struct proc{
    struct proc *next; // next proc pointer
    int *ksp; // saved sp: at byte offset 4         ksp 保存堆栈指针,以便进程恢复。当进程放弃CPU时,会将上下文保存在堆栈中。
    int pid; // process ID                          pdi 进程id编号
    int ppid; // parent process pid                 ppid 父进程id编号
    int status; // PROC status=FREE|READY, etc.     status 进程当前状态
    int priority; // scheduling priority            priority 进程调度优先级
    int kstack[1024]; // process execution stack    kstack[1024]进程执行时的堆栈
}PROC;

fork函数

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

int main(){
    printf("before fork 1\n");
    printf("before fork 2\n");
    printf("before fork 3\n");

    pid_t pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }else if(pid ==0 ){
        printf("---child : my id = %d , myparent id = %d\n",getpid(),getppid());
    }else if(pid >0){
        printf("---parent : my child id = %d,my id = %d , my parent id = %d\n",pid,getpid(),getppid());
    }
    printf("=========EOF==========\n");
    return 0;
}

fork进阶版(详细理解参考https://www.cnblogs.com/love-jelly-pig/p/8471206.html)

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


int main()
{
    int i = 0;
    printf("i som/pa ppid pid fpid/n");
    for(i = 0;i<2;i++)
    {
        pid_t fpid = fork();
        if(fpid==0)
        {
	    printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid);
	}
	else
	{
	    printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
	}
    }
    return 0;
}



多任务处理系统

多任务处理(MT)系统,说明多任务处理、上下文切换和进程处理原则。下面的程序实现了一个模拟操作系统内核模式各项操作的多任务环境,由以下几个部分组成。

type.h 文件

/*********** type.h file ************/
#define NPROC    9               // number of PROCs
#define SSIZE 1024               // gtack size = 4KB

// PROC status 
#define FREE     0
#define READY    1
#define SLEEP    2
#define ZOMBIE   3

typedef struct proc{
  struct proc *next;             // next proc pointer
  int *ksp;                      // saved stack pointer
  int pid;                       // pid = 0 to NPROC-1
  int ppid;                      // parent pid
  int status;                    // PROC status
  int priority;                  // scheduling priority
  int kstack[SSIE]               // process stack
}PROC;

ts.s文件(ts.s是汇编代码,在32位GCC汇编代码中可实现进程上下文切换)

#------------- ts.s file file -----------------
       .globl running, scheduler, tswitch
tswitch:
SAVE:  pushl %eax
       pushl %ebx
       pushl %ecx
       pushl %edx
       pushl %ebp
       pushl %esi
       pushl %edi
       pushfl
       movl running, %ebx  # ebx -> PROC
       movl %esp, 4(%ebx)   # PORC.save_sp = esp
FIND:  call scheduler
RESUME:movl running, %ebx  # ebx -> PROC
       movl 4(%ebx), %esp  #esp = PROC.saved_sp
       popf1
       popl %edi
       popl %esi
       popl %ebp
       popl %edx
       popl %ecx
       popl %ebx
       popl %eax
       ret
# stack contents = |retpc|eax|ebx|ecx|edx|ebp|esi|edi|eflag|
#                    -1   -2  -3  -4  -5  -6  -7  -8   -9

queue.c文件

/******************************* queue.c file *******************************/
int enqueue(PROC **queue,PROC *p)
{
    PROC *q = *queue;
    if(q == 0 || p->priority> q->priority){
        *queue = p;
        p->next = q;
    }
    else{
        while(g->next && p->priority <= q->next->priority)
            q = q->next;
        p->next = q->next;
        q->next = p;
    }
}
PROC *dequeue (PROC **queue)
{
    PROC *p = *queue;
    if (p)
        *queue =(*queue)->next;
    return p;
}
int printList(char *name,PROC *p)
{
    printf("%s = ",name);
    while(p){
        printf("[8d %d]->",p->pid,p->priority);
        p = p->next;
    }
    printf("NULL\n");
}

linux中subreaper 进程

SUBREAPER 设置祖父进程接管孙子进程

测试:

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

int main(){
    pid_t pid = fork();
    if(pid<0) return -2;
    if(pid==0){ //child
        printf("child, pid = %d, ppid = %d\n",getpid(),getppid());
        pid = fork();
        if(pid<0) return -3;
        if(pid==0){ //grandchild
            sleep(1);
            printf("grandchild, pid = %d, ppid=%d\n",getpid(),getppid());
        }else{
            exit(0);
        }
        exit(0);
    }else{//parent
        printf("parent, pid = %d\n",getpid());
        sleep(2);
    }
    printf("exit from parent\n");
    return 0;
}

上述测试程序中,grandchild 是从 child 那里 fork 出来的,正常情况下,当child 退出后,grandchild 的父进程为pid为1的 init,这样parent进程就无法收接收来自grandchild 的 SIGCHLD信号,程序运行结果如下:

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

int main(){
    int ret = prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
    if(ret<0) return -1;
    pid_t pid = fork();
    if(pid<0) return -2;
    if(pid==0){ //child
        printf("child, pid = %d, ppid = %d\n",getpid(),getppid());
        pid = fork();
        if(pid<0) return -3;
        if(pid==0){ //grandchild
            printf("before child exit, grandchild, pid = %d, ppid = %d\n",getpid(),getppid());
            sleep(2);
            printf("after child exit, grandchild, pid = %d, ppid = %d\n",getpid(),getppid());
        }else{
            sleep(1);
            exit(0);
        }
        exit(0);
    }else{//parent
        printf("parent, pid = %d\n",getpid());
        sleep(3);
    }
    printf("exit from parent\n");
    return 0;
}

i/o重定向和系统调用

在Unix系统中,每个进程都有STDIN、STDOUT和STDERR这3种标准I/O,它们是程序最通用的输入输出方式。C语言可以通过scanf("%s", str);
从终端输入字符串,通过printf("%s\n", str);向终端输出字符串。理解I/O重定向的原理需要从Linux内核为进程所维护的关键数据结构入手。
对Linux进程来讲,每个打开的文件都是通过文件描述符(FD)来标识的,内核为每个进程维护了一个文件描述符表,这个表以FD为索引,再进一步
指向文件的详细信息。在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR,如下图所示意:
所谓的I/O重定向也就是让已创建的FD指向其他文件。(下面是对STDOUT重定向到testfile.txt前后内核文件描述符表变化的示意图)

编写一个C程序,通过调用sort这个Shell命令进行排序,要求把in.txt和out.txt分别重定向到sort的STDIN,STDOUT。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
    int pid = 0;
    // fork a worker process
    if (pid = fork()) {
        // wait for completion of the child process
        int status; 
        waitpid(pid, &status, 0);
    }
    else {
        // open input and output files
        int fd_in = open("in.txt", O_RDONLY);
        int fd_out = open("out.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        if (fd_in > 0 && fd_out > 0) {
            // redirect STDIN/STDOUT for this process
            dup2(fd_in, 0);
            dup2(fd_out, 1);  
            // call shell command
            system("sort");
            close(fd_in);
            close(fd_out);
        }
        else {
            // ... error handling
        }
    }
    return 0;
}

上面的主要步骤包括:

  1. 首先fork一个子进程,后续步骤都在子进程中完成,父进程通过waitpid()系统调用等待子进程结束;
  2. 打开open()系统调用打开in.txt和out.txt,得到它们的描述符(在我的测试中,这两个值通常为3和4);
  3. 通过dup2()系统调用把STDIN重定向到fd_in,把STDOUT重定向到fd_out(注意,重定向的影响范围是整个子进程);
  4. 通过system()系统调用运行shell命令sort

管道(参考:https://zhuanlan.zhihu.com/p/58489873)

什么是管道

管道,英文为pipe。这是一个我们在学习Linux命令行的时候就会引入的一个很重要的概念。它的发明人是道格拉斯.麦克罗伊,
这位也是UNIX上早期shell的发明人。他在发明了shell之后,发现系统操作执行命令的时候,经常有需求要将一个程序的输出
交给另一个程序进行处理,这种操作可以使用输入输出重定向加文件搞定。

程序实例

fork产生的子进程会继承父进程对应的文件描述符。利用这个特性,父进程先pipe创建管道之后,子进程也会得到同一个管道的
读写文件描述符。从而实现了父子两个进程使用一个管道可以完成半双工通信。此时,父进程可以通过fd[1]给子进程发消息,子
进程通过fd[0]读。子进程也可以通过fd[1]给父进程发消息,父进程用fd[0]读。程序实例如下:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
​
#define STRING "hello world!"
​
int main()
{
    int pipefd[2];
    pid_t pid;
    char buf[BUFSIZ];
​
    if (pipe(pipefd) == -1) {
        perror("pipe()");
        exit(1);
    }
​
    pid = fork();
    if (pid == -1) {
        perror("fork()");
        exit(1);
    }
​
    if (pid == 0) {
        /* this is child. */
        printf("Child pid is: %d\n", getpid());
        if (read(pipefd[0], buf, BUFSIZ) < 0) {
            perror("write()");
            exit(1);
        }
​
        printf("%s\n", buf);
​
        bzero(buf, BUFSIZ);
        snprintf(buf, BUFSIZ, "Message from child: My pid is: %d", getpid());
        if (write(pipefd[1], buf, strlen(buf)) < 0) {
            perror("write()");
            exit(1);
        }
​
    } else {
        /* this is parent */
        printf("Parent pid is: %d\n", getpid());
​
        snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());
        if (write(pipefd[1], buf, strlen(buf)) < 0) {
            perror("write()");
            exit(1);
        }
​
        sleep(1);
​
        bzero(buf, BUFSIZ);
        if (read(pipefd[0], buf, BUFSIZ) < 0) {
            perror("write()");
            exit(1);
        }
​
        printf("%s\n", buf);
​
        wait(NULL);
    }
​
​
    exit(0);
}

posted on 2022-10-09 16:56  20201310寸头  阅读(29)  评论(0编辑  收藏  举报