个人知识库 个人知识库
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub

Agnes001

坚持是一件很伟大的事业
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub
  • C语言

  • CPlusPlus

  • Lua技术栈

  • edoyun

  • 内存管理

  • 数据结构

  • 网络编程

  • Linux

    • Linux基础
    • 系统编程

    • 基础命令
    • itcast
    • 文件io
    • gdb
    • Ubuntu安装eclipse
    • gcc安装
    • 系统编程
      • 进程体系与进程管理
        • 进程控制块PCB
        • 进程控制fork 复制
        • exec函数族 李代桃僵
        • 进程回收
        • 实现简单的shell
      • 网络IO模型
      • 智能指针
    • linux内核多线程
  • 池化技术

  • 操作系统

  • python

  • 编程技术
  • Linux
Agnes001
2022-04-23

linux系统编程

# 线程安全,对文件加锁

# 进程体系与进程管理

https://blog.csdn.net/qq_37233070/article/details/122519360

https://blog.51cto.com/dlican/3744943

https://xiaolong.blog.csdn.net/category_3278435.html

https://www.zhihu.com/question/30101368/answer/2427069276

PCB进程控制块,fork创建,exec调度,同步,通信

# 进程控制块PCB

task_struct结构体: 进程id 进程的状态,有运行、挂起、停止、僵尸等状态。 进程切换时需要保存和恢复的一些CPU寄存器 描述虚拟地址空间的信息 描述控制终端的信息(记录终端,因为会给终端打印消息) 当前工作目录 umask掩码 文件描述符表,包含很多指向file结构体的指针 和信号相关的信息 用户id和组id Session和进程组 进程可以使用的资源上限

各个进程的地址空间中的数据是完全独立的。

# 进程控制fork 复制

fork的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程(Parent Process),新进程称为子进程(Child Process)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程开始一个一个复制出来的。 pstree命令可以看到进程间的关系。 在shell下输入命令可以运行一个进程,是因为shell进程在读取用户输入的命令之后会调用fork复制出一个新的shell进程。

  • fork函数原型
pid_t fork(void);
//成功后子进程返回0,父进程返回子进程id,出错返回-1

fork/getpid/getppid

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

int main()
{
    pid_t pid = fork();

    if(pid)
    {
        printf("self   process id: %d\n", getpid());
        printf("father process id: %d\n", getppid());
        printf("child  process id: %d\n", pid);
    }else{
        printf("create child process ^_^\n");
    }

    return 0;
}

# exec函数族 李代桃僵

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

取代当前进程

#include <unistd.h>
extern char **environ;
//pathname: 要执行的程序的绝对路径
//第一个arg: 占位
//后边的arg: 命令的参数
//参数写完之后要加NULL
//例 execl("/bin/ls", "ls", "-lah", NULL);
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
//执行PATH环境变量能够搜索到的程序
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
//char* args = ["ps", "aux", NULL];
//execv("/bin/ps", args);
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[]);

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

环境变量 extern char **environ; exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数。和命令行参数argv类似,环境变量表也是一个字符串数组 name=value

可以用getenv函数,查找对应的name的value

#include <stdlib.h>
char *getenv(const char *name);

可以用setenv函数,将环境变量的name的值设置为value

#include <stdlib.h>
int setenv(const char *name, const char *value, int rewrite);
void unsetenv(const char *name);

gdb调试多进程

跟着父进程走 set follow-fork-mode parent 跟着子进程走 set follow-fork-mode child

通过在gdb中设置follow-fork-mode和detach-on-fork的值来调试子程序, 在gdb中分别默认的值是: parent和on:

follow-fork-mode detach-on-fork 效果 parent on 只调试主进程(GDB默认) child on 只调试子进程 parent off 同时调试两个进程, gdb跟主进程, 子进程block在fork位置 child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置

# 进程回收

# 孤儿进程

父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程 为了释放子进程的系统资源,PCB必须由父进程释放

# 僵尸进程

子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

# wait和waitpid函数 收拾残局

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配到内存,但它的PCB还保留着,内核在其中保存了一些信息:如果时正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。

父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程

#include <sys/types.h>
#include <sys/wait.h>
//status:判断子进程是如何死的 - 正常退出/被某个信号杀死了
//调用一次只能回收一个子进程, 返回值-1表示没有子进程
//WIFEXITED(status) 为非0	→ 进程正常结束
//	WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
//WIFSIGNALED(status) 为非0 → 进程异常终止
//	WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
pid_t wait(int* status);//等待所有子进程中的任何一个死亡,阻塞等待
//option: WNOHANG非阻塞,0阻塞
pid_t waitpid(pid_t pid, int* status, int option);//指定可以收谁的尸体

例如,一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时彻底 注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

int status;
pid_t wpid;
while((wpid = wait(&status)) != -1){
    printf("child died pid = %d\n", wpid);
    if(WIFEXITED(status)){
        printf("return value %d\n", WEXITSTATUS(status));
    }else if(WIFSIGNALED(status)){
        printf("return value %d\n", WTERMSIG(status));
    }
}

ps aux | grep "xxx" x 显示不依赖终端的进程 ps ajx | grep "xxx" kill -l 查看所有信号 kill发信号 杀死某个进程 kill -9 pid

=====段错误===

  1. 访问了非法内存
  2. 访问了不可写的区域进行写操作
  3. 栈空间溢出

# 实现简单的shell

识别和处理以下符号: 简单的标准输入输出重定向(<和>) 管道(|):shell进程先调用pipe创建一对管道描述符,然后fork出两个子进程,一个子进程关闭读端,调用dup2把写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而shell进程把管道的两端都关闭,调用wait等待两个子进程终止。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LEN 1024

char* trim(char* str){
    int head = 0;
    int tail = strlen(str)-1;
    while(isspace(str[head++]));
    while(isspace(str[tail])) str[tail--] = 0;
    return str+head;    
}

int main()
{
    char buf[LEN];
    while(1){
        printf("kkb$");
        fgets(buff, LEN, stdin);
        buff[strlen(buff)-1] = 0;

        if(!strcmp(buff,"exit"))
        {
            printf("exit~\n");
            break;
        }
        //创建子进程
        pid_t pid = fork();
        if(pid<0)
        {
            perror("fork");
            exit(1);
        }
        //父进程 等待子进程结束,继续下一轮
        if(pid){
            wait(NULL);
            continue;
        }

        //子进程 redirect fd重定向
        int redfd = -1;
        if(strstr(buff,"<")){
            redfd = 0;
        }
        if(strstr(buff,">")){
            redfd = 1;
        }

        char* cmd=NULL;
        if(-1 != redfd){
            char* token = NULL;
            token = strtok (str,"<>");
            cmd = token;
            while (token != NULL)
            {
                printf ("%s\n",token);
                token = strtok (NULL, "<>");
            }
            token = trim(token);
            int fd;
            if(redfd)
                fd = open(token, O_RDWR | O_CREAT, 0644);
            else
                fd = open(token, O_RDWR);
            
            if(fd<0){
                perror(token);
                exit(1);
            }

            printf("open %s successfully~\n",token);

            dup2(fd, redfd);//将前面的重定向到后边

        }else{
            cmd = buff;
        }
        
        int i = 0;
        char* argarr[20];
        char* tk=strtok(cmd," ");
        while(tk){
            argarr[i++] = tk;
            tk=strtok(NULL, "");
        }
        argarr[i]=NULL;
        execv(argarr[0],argarr);
        perror("exec");
        exit(1);
    }
}

# 网络IO模型

https://baijiahao.baidu.com/s?id=1702203870868277915&wfr=spider&for=pc 5种Linux网络IO模型包括:同步阻塞IO、同步非阻塞IO、多路复用IO、信号驱动IO和异步IO。

实践:https://zhuanlan.zhihu.com/p/150972878

Linux编程 基本使用 不同系统下的安装 vscode的使用 cmake详解 基础命令 常用命令 man ls引申硬/软链接 gdb 系统编程 文件io 文件系统 进程 线程 网络编程

C语言 编译过程 ESc 基本数据结构 枚举 https://www.bilibili.com/read/cv9652772 数组+指针+结构体 链表 链表相关操作 插入(头插法/尾插法) 队列、栈的实现 常用头文件 io相关操作 C string time stdlib

CPlusPlus 面向对象特性 友元 继承 多态 设计模式 Container allocator vector/queue/stack/... 多线程 thread 常用库 std::chrono 日期时间库 iostream C++11特性(程序喵大人) auto和decltype 右值 智能指针 std::ref std::bind和std::thread传递引用参数需要用到 lambda ... 其他库的使用 json libcurl log4cplus drogon

PHP

项目实战 高并发服务器 QT PyQt5 数据

工具 Git使用 Git结构 指令

数据结构 算法

# 智能指针

详讲:https://zhuanlan.zhihu.com/p/436290273

实践:https://blog.csdn.net/albertsh/article/details/82286999

# C++并发编程

http://shouce.jb51.net/cpp_concurrency_in_action/

编辑此页
gcc安装
linux内核多线程

← gcc安装 linux内核多线程 →

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式