linux-c进程笔记

linux-c进程笔记

进程状态

三态模型

就绪态运行态阻塞态

五态模型

状态名称含义
新建态进程刚被创建,尚未加入就绪队列
就绪态具备运行条件,等待系统分配资源后运行
运行态具备运行条件,正在运行
阻塞态不具备运行条件,等待运行
终止态进程完成任务到达正常结束点,或出现错误或异常而被终止

进程创建

导入头文件sys/type.hunistd.h,使用fork()函数

#include <iostream>
#include <sys/types.h>
#include <unistd.h>

void test() {
    for(int i = 0; i < 5; i++) {
        std::cout << i << std::endl;
        sleep(1);
    }
}

void fork_test() {
    // 创建新进程
    // fork函数返回值会返回两次,一次在父进程中,一次在子进程中,在父进程中返回子进程的ID,在子进程中返回0
    // 父进程中返回-1表示创建失败
    pid_t pid = fork();
    
    // 判断是父进程还是子进程
    if(pid > 0) {
        std::cout << "Parent process, pid: " << getpid() << ", ppid: " << getppid() << std::endl;
    } else if (pid == 0) {
        std::cout << "Clild process, pid: " << getpid() << ", ppid: " << getppid() << std::endl;
    } else {
        std::cout << "create process error" << std::endl;
    }
    test();
}

int main() {
    fork_test();
    return 0;
}

fork函数在调用时会将父进程的虚拟地址空间进行复制,采取读时共享,写时复制的策略

exec函数族

使用exec函数后,会替换剩余的代码

execl执行指定的可执行文件
execlp执行环境变量中的指定可执行文件
execv在给出的目录列表中寻找指定的可执行文件并执行
void exec_test() {
    pid_t pid = fork();
    if(pid > 0) {
        std::cout << "Parent process, pid: " << getpid() << ", ppid: " << getppid() << std::endl;
    } else if (pid == 0) {
        std::cout << "Clild process, pid: " << getpid() << ", ppid: " << getppid() << std::endl;
        // 只有出错才有返回值 
        execl("test", "test", NULL);
    } else {
        std::cout << "create process error" << std::endl;
    }
    test();
}

int main() {
    using namespace std;
    cout << "test" << endl;
    exec_test();
    return 0;
}

进程控制

进程退出

void exit(int status)调用前会刷新I/O缓冲区
void _exit(int status)不会刷新缓冲区

孤儿进程

父进程运行结束,子进程依然在进程,则子进程被成为孤儿进程

每当出现孤儿进程时,内核会把孤儿进程的父进程设置为init,在孤儿进程运行结束时,init会回收其资源

僵尸进程

进程终止时,父进程尚未回收,子进程残留资源(PCB)在内核中,变成僵尸进程

僵尸进程无法被kill -9杀死

父进程使用wait()waitpid()释放子进程的残留资源

wait()阻塞
waitpd()可以设置不阻塞,可设置回收指定子进程

进程间通信(IPC)

同一主机进程间通信

unix进程间通信方式

匿名(无名)管道

有名管道

信号

System V进程间通信方式/POSix进程间通信方式

消息队列

共享内存

信号量

不同主机进程间通信

socket

匿名管道

UNIX系统IPC最古老的方式

# 创建ls和wc两个进程,将ls的输出作为wc的输入
ls | wc -l

匿名管道本质时内核内存中维护的缓冲器

匿名管道拥有文件的特质,可以进行读操作和写操作,但没有文件实体,有名管道有文件实体,但不存储数据。可以使用操作文件的方式对管道进行操作

管道是一个字节流,使用管道时不存在消息或消息边界的概念,从管道数据的进程可以读取任意大小的数据块,而不管管道中数据块的大小时多少

通过管道的数据时顺序的,先进先出,传递方向是单向的,一端写入一端写出,为半双工通信

管道中的数据是一次性的,一旦读取则被抛弃,无法使用lseek()来随机访问数据

匿名管道是能在具有公共祖先的进程间使用

使用pipe()函数

读管道:

管道中有数据,read返回实际读到的字节数

管道中无数据:

写端被全部关闭,read返回0(文件末尾)

写端没有完全关闭,read阻塞等待

写管道:

管道读端全部关闭,进程异常终止(进程收到SIGPIPE信号)

管道读端没有全部关闭:

管道已满,write阻塞

管道没有满,write将数据写入,并返回实际写入的字节数

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

void pipe_test() {

    int pipefd[2];
    // pipefd[0]为读端,pipefd[1]为写端
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    pid_t pid = fork();
    if(pid > 0) {
        char buf[1024] = {0};
        int len = read(pipefd[0], buf, sizeof(buf));
        printf("chind recv: %s pid: %d\n", buf, getpid());
        memset(buf, 0, 1024);

        char * s = "Hello, child, I am parent.";
        write(pipefd[1], s, strlen(s));

        wait(NULL);
    } else if(pid == 0) {
        char * s = "Hello, I am child.";
        write(pipefd[1], s, strlen(s));
        sleep(1);

        char buf[1024] = {0};
        int len = read(pipefd[0], buf, sizeof(buf));
        printf("parent recv: %s pid: %d\n", buf, getpid());
        memset(buf, 0, 1024);
    } else {
        perror("fork");
    }
}

int main() {
    pipe_test();
    return 0;
}

有名管道

以FIFO的文件形式存在于文件系统中,类型为管道文件,只要可以访问文件路径,就可以通过FIFO通信

一旦打开了FIFO就能在它上面使用与操作匿名管道和其他文件系统调用一样的I/O系统(read(), write(), close())。

FIFO和匿名管道一样分输入端和输出端,先进先出

FIFO有文件实体,但是没有数据放入

# 创建有名管道
mkfifo [name]

使用mkfifo()函数创建有名管道

一个为只读而打开一个管道的进程会阻塞,知道另一个进程为只写打开管道

一个为只写而打开一个管道的进程会阻塞,知道另一个进程为只读打开管道

读管道:

管道中有数据,read返回实际读到的字节数

管道中无数据:

写端被全部关闭,read返回0(文件末尾)

写端没有完全关闭,read阻塞等待

写管道:

管道读端全部关闭,进程异常终止(进程收到SIGPIPE信号)

管道读端没有全部关闭:

管道已满,write阻塞

管道没有满,write将数据写入,并返回实际写入的字节数

/**
 * fifo_write.c
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <wait.h>
#include <string.h>
#include  <sys/stat.h>
#include <fcntl.h>

void writes() {
    int fd = open("fifo1", O_WRONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    for(int i = 0; i < 10; i++) {
        char buf[1024] = {0};
        sprintf(buf, "hello, %d\n", i);
        printf("write data: %s", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }
}

void fifo_tes() {
    if(access("fifo1", F_OK) == -1) {
        printf("管道不存在。\n");
        int ret = mkfifo("fifo1", 0664);
        if(ret < 0) {
            perror("mkfifo");
        exit(0);
        }
    }
}

int main() {
    fifo_tes();
    writes();
    return 0;
} 
/**
 * fifo_read.c
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <wait.h>
#include <string.h>
#include  <sys/stat.h>
#include <fcntl.h>

void reads() {
    int fd = open("fifo1", O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }
    while(1) {
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if(len == 0) {
            printf("写端连接断开。\n");
            exit(0);
        }
        printf("read data: %s", buf);
        sleep(1);
    }
}

void fifo_tes() {
    if(access("fifo1", F_OK) == -1) {
        printf("管道不存在。\n");
        int ret = mkfifo("fifo1", 0664);
        if(ret < 0) {
            perror("mkfifo");
            exit(0);
        }
    }
}

int main() {
    fifo_tes();
    reads();
    return 0;
} 

内存映射

将文件数据映射到内存中
使用mmap()函数开启映射,munmap()关闭映射

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void mmap_test() {
    int fd = open("test.txt", O_RDWR);
    int size = lseek(fd, 0, SEEK_END);

    // 创建内存映射区
    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptr == NULL) {
        perror("mmap");
        exit(0);
    }
    pid_t pid = fork();
    if(pid > 0) {
        strcpy((char*)ptr, "hello, child!\n");
    } else if(pid == 0) {
        char buf[64];
        strcpy(buf, (char*)ptr);
        printf("read data: %s", buf);
    } else {
        perror("fork");
        exit(0);
    }
    munmap(ptr, size);
}

int main() {
    mmap_test();
    return 0;
}
注意事项

mmap权限和open权限不匹配时,产生MAP_FIELD错误

文件偏移量必须是4k的整数倍

mmap调用失败的情况

  1. 第二个参数length=0
  2. 第三个参数prot只指定了写权限

可以open时使用O_CREAT创建新文件,但是文件大小不能为零

mmap后关闭文件描述符,对mmap映射不会产生影响

指针ptr越界产生段错误

内存映射可以实现文件复制

void mmap_anonymous() {
    //匿名映射,不需要文件实体,只能进行父子间通信
    int length = 4096;
    void *ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if(ptr == MAP_FAILED) {
        perror("mmap");
    }
    pid_t pid = fork();
    if(pid > 0) {
        strcpy((char*)ptr, "hello, child!\n");
        wait(NULL);
    } else if(pid == 0) {
        sleep(1);
        char buf[64];
        strcpy(buf, (char*)ptr);
        printf("read data: %s", buf);
    } else {
        perror("fork");
        exit(0);
    }
    munmap(ptr, length);
}

信号

信号时事件发生时对进程的通知机制,有时也被称为软件中断,是再软件参差上对中断机制的一种模拟,是一种异步通信方式。

信号源于内核

对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号,如ctrl+C,通常会给进程发送中断信号

硬件发生异常,硬件检测到了一个错误条件并通知内核,随机再由内核发送相应信号给相关进程

系统状态变化

运行kill命令或调用kill()函数

使用信号的主要目的
  1. 让进程知道已经发生了一个特定的事
  2. 强迫进程执行他自己代码中的信号处理程序
信号的特点
  1. 简单
  2. 不能携带大量信息
  3. 满足某个特定条件才发送
  4. 优先级较高

查看系统定义的信号列表:kill -l

前31个为常规信号,其余为实时信号

信号的5种默认处理动作

名称含义
Term终止进程
Ign当前进程忽略掉这个信号
Core终止进程,并生成一个Core文件
Stop暂停当前进程
Cont继续执行当前被暂停的进程

信号的三种状态:产生,未决,递达

信号函数
kill()给指定进程或进程组发送指定信号
raise()给当前进程发送指定信号
abort()杀死指定进程,相当于发送SIGABRT信号
alarm()计时n秒后给当前线程发送信号SIGALRM,默认终止进程
setitimer()设置定时器,微妙级别精度,可以实现周期性定时
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

void kill_test() {
    // 给任何进程或进程组pid发送任何信号sig
    // pid > 0:将信号发送给指定的进程
    // pid = 0:将信号发送给当前进程组
    // pid = -1:将信号发送给每一个由权限接受这个信号的进程
    // pid < -1: 发送给指定进程组,pid取相反数
    pid_t pid = fork();
    if(pid > 0) {
        int i = 0;
        for(int i = 0; i < 5; i++) {
            printf("child process\n");
            sleep(1);
        }
    } else if(pid == 0) {
        printf("parent process\n");       
        sleep(2);
        printf("kill child process\n");
        int ret = kill(pid, SIGINT);
    }
}

int main () {
    kill_test();
    return 0;
}
信号捕捉
signal()捕捉指定信号并设置处理方式
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/time.h>

void myalarm(int num) {
    printf("捕捉到信号: %d\n", num);
}

void signal_test() {
    // 捕捉信号
    // signum 被捕捉的信号类型
    // handler: 被捕捉的信号的处理方式
    //  - SIG_IGN: 忽略信号
    //  - SIG_DEF:默认处理方式
    //  - 回调函数:自定义处理方式,使用函数指针,由内核调用
    // 返回值: 上一次注册的信号处理函数的地址,第一次返回NULL,失败返回SIG_ERR
    signal(SIGALRM, myalarm);
}

void setitimer_test() {
    struct itimerval new_value;
    // 设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟时间,3秒后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    // 非阻塞定时器
    int ret = setitimer(ITIMER_REAL, &new_value, NULL);
    printf("开始定时\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    getchar();
}

int main () {
    signal_test();
    setitimer_test();
    return 0;
}
信号集

多个信号的组合,许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其数据类型为sigset_t

阻塞信号集:阻塞是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生,可以手动设置

未决信号集:未决是一种状态,指的是信号从产生到信号被处理前的这一段时间,不能手动设置

sigemptyset()清空信号集中的数据,将信号集中所有的标志位设置为0
sigfillset()将信号集中所有的标志位设置为1
sigaddset()设置信号集中指定的信号标志位为1,设置为阻塞
sigdelset()设置信号集中指定的信号标志位为0
sigismemberset()判断某个信号是否为阻塞
sigprocmask()获取/设置内核中的阻塞信号集
sigpending()获取内核中的未定信号集
sigaction()信号捕捉,检查或改变信号的处理
SIGCHLD信号

SIGCHLD信号产生的条件,子进程给父进程发送SIGCHLD,父进程默认忽略

  1. 子进程终止时
  2. 子进程接收到SIGSTOP信号停止时
  3. 子进程处在停止态,接收到SIGCONT后唤醒时

捕捉SIGCHLD信号可用于处理僵尸进程

/*
    SIGCHLD信号产生的3个条件:
        1.子进程结束
        2.子进程暂停了
        3.子进程继续运行
        都会给父进程发送该信号,父进程默认忽略该信号。
    
    使用SIGCHLD信号解决僵尸进程的问题。
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

void myFun(int num) {
    printf("捕捉到的信号: %d\n", num);
    // 回收子进程PCB的资源
    // while(1) {
    //     wait(NULL); 
    // }
    while(1) {
       int ret = waitpid(-1, NULL, WNOHANG);
       if(ret > 0) {
           printf("child die , pid = %d\n", ret);
       } else if(ret == 0) {
           // 说明还有子进程活着
           break;
       } else if(ret == -1) {
           // 没有子进程
           break;
       }
    }
}

int main() {

    // 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);

    // 创建一些子进程
    pid_t pid;
    for(int i = 0; i < 20; i++) {
        pid = fork();
        if(pid == 0) {
            break;
        }
    }

    if(pid > 0) {
        // 父进程

        // 捕捉子进程死亡时发送的SIGCHLD信号
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);
        sigaction(SIGCHLD, &act, NULL);

        // 注册完信号捕捉以后,解除阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        while(1) {
            printf("parent process pid : %d\n", getpid());
            sleep(2);
        }
    } else if( pid == 0) {
        // 子进程
        printf("child process pid : %d\n", getpid());
    }

    return 0;
}

共享内存

共享内存允许多个进程共享物理内存的同一块区域(段)

共享内存为进程用户空间的一部分,所以无需内核介入

shmget()使用创建新的共享内存段或获取已有的共享内存段标识符
shmat()是共享内存段成为调用进程的虚拟内存的一部分,返回该内存区的地址
shmdt()分离共享内存段,进程终止时自动完成这一步
shctl()删除共享内存段,只有当所有进程都与之分离后才能销毁
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
    // - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
    //     新创建的内存段中的数据都会被初始化为0
    // - 参数:
    //     - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
    //             一般使用16进制表示,非0值
    //     - size: 共享内存的大小
    //     - shmflg: 属性
    //         - 访问权限
    //         - 附加属性:创建/判断共享内存是不是存在
    //             - 创建:IPC_CREAT
    //             - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
    //                 IPC_CREAT | IPC_EXCL | 0664
    //     - 返回值:
    //         失败:-1 并设置错误号
    //         成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。


void *shmat(int shmid, const void *shmaddr, int shmflg);
    // - 功能:和当前的进程进行关联
    // - 参数:
    //     - shmid : 共享内存的标识(ID),由shmget返回值获取
    //     - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
    //     - shmflg : 对共享内存的操作
    //         - 读 : SHM_RDONLY, 必须要有读权限
    //         - 读写: 0
    // - 返回值:
    //     成功:返回共享内存的首(起始)地址。  失败(void *) -1


int shmdt(const void *shmaddr);
//     - 功能:解除当前进程和共享内存的关联
//     - 参数:
//         shmaddr:共享内存的首地址
//     - 返回值:成功 0, 失败 -1

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    // - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。
    // - 参数:
    //     - shmid: 共享内存的ID
    //     - cmd : 要做的操作
    //         - IPC_STAT : 获取共享内存的当前的状态
    //         - IPC_SET : 设置共享内存的状态
    //         - IPC_RMID: 标记共享内存被销毁
    //     - buf:需要设置或者获取的共享内存的属性信息
    //         - IPC_STAT : buf存储数据
    //         - IPC_SET : buf中需要初始化数据,设置到内核中
    //         - IPC_RMID : 没有用,NULL

key_t ftok(const char *pathname, int proj_id);
//     - 功能:根据指定的路径名,和int值,生成一个共享内存的key
//     - 参数:
//         - pathname:指定一个存在的路径
//             /home/nowcoder/Linux/a.txt
//             / 
//         - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
//                    范围 : 0-255  一般指定一个字符 'a'

使用ipcs -m查看共享内存信息

使用ipcsrm -M [key]/-m [shmid]删除共享内存

守护进程

守护进程又称Daemon进程,是Linux中后台的服务进程,独立于控制终端且周期性执行某种任务会等待处理某些发生的事件,一般以d结尾

守护进程在系统启动时被创建并一直运行至系统关闭

守护进程不拥有控制终端,不会收到任何控制信号和终端相关的信号

/*
    写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
*/

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

void work(int num) {
    // 捕捉到信号之后,获取系统时间,写入磁盘文件
    time_t tm = time(NULL);
    struct tm * loc = localtime(&tm);
    // char buf[1024];

    // sprintf(buf, "%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon
    // ,loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);

    // printf("%s\n", buf);

    char * str = asctime(loc);
    int fd = open("time.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
    write(fd ,str, strlen(str));
    close(fd);
}

int main() {

    // 1.创建子进程,退出父进程
    pid_t pid = fork();

    if(pid > 0) {
        exit(0);
    }

    // 2.将子进程重新创建一个会话
    setsid();

    // 3.设置掩码,确保拥有所需权限
    umask(022);

    // 4.更改工作目录
    chdir("/home/nowcoder/");

    // 5. 关闭、重定向文件描述符
    int fd = open("/dev/null", O_RDWR);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);

    // 6.业务逻辑

    // 捕捉定时信号
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = work;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM, &act, NULL);

    struct itimerval val;
    val.it_value.tv_sec = 2;
    val.it_value.tv_usec = 0;
    val.it_interval.tv_sec = 2;
    val.it_interval.tv_usec = 0;

    // 创建定时器
    setitimer(ITIMER_REAL, &val, NULL);

    // 不让进程结束
    while(1) {
        sleep(10);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值