《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;
}
上面的主要步骤包括:
- 首先fork一个子进程,后续步骤都在子进程中完成,父进程通过waitpid()系统调用等待子进程结束;
- 打开open()系统调用打开in.txt和out.txt,得到它们的描述符(在我的测试中,这两个值通常为3和4);
- 通过dup2()系统调用把STDIN重定向到fd_in,把STDOUT重定向到fd_out(注意,重定向的影响范围是整个子进程);
- 通过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);
}