zoukankan      html  css  js  c++  java
  • Linux进程学习

    进程与进程管理

    清屏:system("clear");  //#include <signal.h>

    进程环境与进程属性

    什么是进程:简单的说,进程就是程序的一次执行过程。

    进程的状态:进程基本三态:运行态、就绪态、封锁态(等待态)——状态可在一定的条件下变化。(其中处于等待的进程分为:可中断进程可以被信号中断其等待;不可中断进程在任何情况下都不可被中断,一直等待条件的满足)。僵死进程:子进程先于父进程退出;从子进程终止到父进程调用wait()之前的时间段内,子进程被称为僵死进程zombie(因为只有父进程能回收,却没有回收其资源,所以父进程一定要wait,不然子进程就真的僵死了)/ 孤儿进程:父进程先于子进程退出,子进程被称为孤儿进程(由进程0/init进程来回收其资源)

    进程的执行模式与类型:执行模式分为用户模式—中断/系统调用—>内核模式。进程类型分为用户进程—中断/系统调用—>系统进程。

    进程的属性:查看进程的属性:非实时ps aux/auxf、实时top。每一列的含义(有空再看)。

    会话(进程组的集合):一个会话可以没有控制终端,也可以与一个终端建立连接,建立连接的会话首进程称为控制进程;一个会话中的几个进程组,可分为一个前台进程组(当会话带有一个终端时,才有前台进程组)和多个后台进程组。其特点:无论何时在终端输入中断信号(ctrl+c)或程序退出信号(ctrl+z),就会有中断信号或退出信号发送给前台进程组的所有进程;进程组中,只要有一个进程存在,该进程组就存在。

    通过进程的PID获取/设置进程组号(PGID):getpgid(pid) / int setpgid(pid, pgid)。

    获取进程的会话号:pid_t getsid(sid),返回会话号;创建新的会话(而不是修改):pid_t setsid()返回进程号,该进程作为会话首进程。

    终端控制函数(返回或操作对象是前台进程组号):pid_t tcgetpgrp(文件描述符)、pid_t tcsetpgrp(文件描述符,pid_t pgrpid)、pid_t tcgetsid(文件描述符)。文件描述符是终端的文件,即通过终端的文件描述符来获得/设置终端的前台进程组号(如果是设置,那么这个新的前台进程组也应该要是同一会话中的进程组),以及获得终端对应的会话ID。

    获得进程用户属性:

    真实用户号(RUID),即创建进程的用户ID:uid_t getuid()。

    有效用户号(EUID),一些情况与UID相同:uid_t geteuid()。——>setuid?

    进程用户号(GID),即创建进程的用户所在组号GID:uid_t getgid()。

    有效进程用户组号(EGID),一些情况与GID相同:uid_t getegid()。——>setgid?

    进程的管理

    创建进程:

    pid_t fork();执行成功后,如果父进程抢到了执行权,则返回子进程的PID,如果子进程抢到了执行权,则返回0,否则执行失败,返回-1;这个函数是系统调用,对父进程复制,所以子进程几乎与父进程一样,代码相同,对文件采用共享方式,但是数据和文件描述符采用独占方式

    vfork()函数与父进程共享数据空间,这种方式执行效率较高,执行顺序是子先父后

    退出进程:

    设置进程即将退出之前,要调用的函数:on_exit(test_exit, (void *)str);该函数有两个参数,第一个是:进程结束前,调用的test_exit函数,如void test_exit(int status, void *arg){...},test_exit函数的第一个参数必须是进程结束时exit(status)的status;第二个(应该不止示例中的参数)的参数就是:传递给test_exit函数的arg,的str。

    进程的正常结束void exit(int status):进程的缓冲区数据自动写回、关闭未关闭的文件。#include <stdlib.h>

    void _exit(int status):进程的缓冲区数据不会自动写回、不会自动关闭文件。#include <unistd.h>

    等待进程(等待子进程中断或结束):pid_t wait(int *status);停止当前进程的执行,直到有信号到来或者其子进程结束。如果子进程已结束,执行wait时会立即返回子进程id和结束状态(保存在参数status中)。status也可以不写,还有一些测试宏(使用需要#include <stdlib.h>,具体有哪些暂时就不管了,考试考到了一看就知道)。有错误(没有子进程)则返回-1。一发现终止的子进程,就根据其pid释放子进程占用的task_struct和系统空间堆栈(就是资源),然后把该子进程的CPU使用时间加到其父进程上。

    替换当前进程:#include <unistd.h>

    默认情况下,新的代码(替换后的代码)仍可以使用,原来的文件描述符(因为并没有关闭),但是可以用fcntl(fd,SETFD,CLOEXE),设置为在执行完exec系列函数后,自动关闭原来打开的文件描述符。多数的exec函数,最后一个参数都是NULL,除了。而且对于所有的exec,若成功替换执行,则此函数不返回,否则返回-1。

    int execl(const char *path, const char *arg, ...),参数:另一个可执行文件(执行这个文件,相当于就是这个进程执行另一个进程的内容,就达到了替换的效果),另一个进程(替换进程)的命令行参数。

    int execlp(const char *file, const char *arg, ...):第一个参数:从PATH环境变量中,查找file这个文件,找到便执行。

    int execv(const char *path, const char *argv[]):与execl不同,execv只有两个参数,因为所有的命令行参数,都放在argv这个二维数组中(其实就是一个字符串数组)。

    int execvp(const char *file, const char *argv[]):execlp ' 1 + execv ' 2 = execvp。

    execve(const char *path, const char *argv[], const char *envp[]):envp成为新的环境变量,即如果替换进程用到环境变量,则以envp为准,如char *envp[] = {"PATH=/bin", 0}。

    进程的调度算法(暂时不管)。

    进程间通信

    一些基本概念:

    进程同步:一个功能未完成,就不返回(一件事做完,才做下一件)
    进程异步:各做各的,每当有一件事做完后就通知调用者。
    IPC:进程间通信。每个IPC机制都有唯一ID,通过KEY创建。同一种机制用ID区分。创建key:ftok(文件名,相同的值(如0xFF)),返回Key。

    三种继承自UNIX的IPC机制:信号、PIPE、FIFO。
    三种继承自System V的IPC机制:信号量、消息队列、共享内存。

    有很多地方的地址都设置为NULL,表示由系统自动确定内存起始地址。

    同主机,进程间数据同步交互机制:无名管道(PIPE)、有名管道(FIFO)、消息队列、内存映射、共享内存,管道和消息队列都自带同步机制,内存映射、共享内存则无同步。

    同主机,进程间异步机制:信号(signal)

    信号安装:

    signal(信号,函数)——>函数可以不带参数,也可以带一个参数(参数为信号,信号就是一堆int值)。

    sigaction(信号,信号结构体,信号结构体)。——>第二个参数安装现在的信号(如果为空指针,则保持原来的信号不变),第三个为之前的信号结构体。

    信号结构体:1、.sa_handler信号处理函数。2、.sa_mask信号处理函数执行过程中,阻塞掉的其他信号。3、.sa_flags用于更改指定信号的行为。

    管道的创建都不用头文件。

    无名管道PIPE:每个管道只有一个内存页面做环形缓冲区,用两个管道可以实现全双工。
    管道的创建:int pipe(int files[2]);参数为文件描述符,0为读,1为写;成功返回0,否则-1;写入的数据被读出就消失。然后就可以通过两个文件描述符files[0]、files[1]进行进程间的数据交互。

    命名管道可保存为文件系统中,的特殊的设备文件,所以进程随时都可以通过命名管道进行I/O操作。
    命名管道创建:通过命令:mkfifo fileName(还可以通过命令使用、删除FIFO);系统调用:int mkfifo(文件名, 管道文件权限),成功则返回文件描述符,否则返回-1。用法:先创建文件(用vi创建都可以,反正共同的文件一定要有),然后就可以得到文件描述符(就和无名一样了,却可读可写)

    内存映射

    将文件或其他对象映射进内存(进程的空间):void *mmap(void *start,length,int prot,int flags,int fd,offset),内存起始地址(一般为NULL),长度(从偏移量offset开始算起),内存访问权限(就是PROT_WRITE、PROT_READ),映射对象的类型(匿名访问适用于亲缘进程,而非亲缘进程一般就选MAP_SHARED),文件描述符,偏移量(一般为0);成功执行则返回映射区的指针,否则返回-1。

                   (内存访问权限prot)                           (映射对象的类型flags)

    让信息立即写回到文件中:int msync(内存起始地址,长度,flag),这个flag和前面的不一样:MS_ASYNC让内核尽快将信息写回文件、MS_SYNC在此函数返回之前将信息写回。

    解除进程空间中的映射关系(释放内存段):int munmap(起始地址,长度),起始地址(mmap返回的指针的值);成功则返回0(解除后内存中的更改才会写回文件),否则返回-1,成功解除之后,如果再访问该映射地址,将会产生段错误。

    使用方法:非亲缘进程:用open打开文件,得到文件描述符fd,再结合mmap建立内存映射,得到共有的内存地址之后就可以通信了(最后一定记到解除映射,关闭文件);亲缘进程就直接匿名访问内存,不用打开文件。

     共享内存

    双方必须要用相同的起始地址的共享内存,否则交互内容不一样
    共享内存使用步骤:
    创建共享内存int shmget(key,size,flag);成功则返回ID号,否则返回-1。
    映射共享内存,将共享内存映射到具体的进程空间void *shmat(id,addr,flag),addr通常为NULL,flag为映射地址访问权限;成功则返回共享内存起始地址,否则返回-1。
    解除映射int shmdt(shmat返回的地址),如果失败则返回-1;两边要同时解除映射。
    共享内存控制(用来删除共享内存就行了)int shmctl(ID,IPC_RMID,0),这一步只需要消费者来扫尾就行了

    shmget函数的flag:

    1. IPC_CREAT:如果共享内存不存在则创建,否则打开。
    2. IPC_EXCL:如果共享内存不存在则创建,否则报错。

    消息队列
    创建和打开消息队列int msgget(key,IPC_CREAT),第二个参数为flag和shmget的flag一样;成功则返回ID号。
    添加/发送消息int msgsnd(ID,结构体缓冲区,大小,0),成功则返回0,否则返回-1。
    读取/接收消息int msgrcv(ID,缓冲区,大小,类型,0),其中类型可以为0,表示可以接收任何类型的消息;成功则返回0,否则返回-1。
    控制消息队列int msgctl(ID,IPC_RMID,0),一般直接用来删除消息队列就行了,所以和删除共享内存的shmctl一样,设置得比较简单。

    //msgsnd函数的第二个参数void *buf :表示自定义的msgbuf结构体
    struct msgbuf { 
            long type; //一般要求接收和发送不一样就行,如send=1,recieve=2
            char msg[自定义大小];
    };

    实际使用(前面说明,后面例子):
    首先使用ftok,创建key,mqkey = ftok(fileName, 0xFF);
    然后用msgget创建消息队列,mqid = msgget(mqkey, IPC_CREAT | IPC_EXCL | 0666);
    设置好缓冲区:struct msgbuf msg; msg.mtype = 随便但一致; 。。。
    然后发送消息msgsnd,msgsnd(mqid, &msg, strlen(msg.mtext) + 1, 0);或者接收消息msgrcv,msgrcv(mqid, &msg, 256, 2, 0);
    最后删除消息队列(至于谁来扫尾),msgctl(mqid, IPC_RMID, NULL);

    同主机,进程间同步机制:信号量(semaphore)

    信号量(互斥和同步):

    //PV操作(原语):P为等待(-),V为信号(+)
    procedure p(var s:samephore){ 
        s.value=s.value-1;
        if (s.value<0)  sleep(s.queue);
    }
    
    procedure v(var s:samephore){
        s.value=s.value+1;
        if (s.value<=0) wakeup(s.queue);
    }
    

    创建一个信号量 / 取得一个信号量的ID:int semget(key,信号量数,flag),信号量数量,一般为1,flag 访问权限 | IPC_CREAT | IPC_EXCL。

    改变信号量的值:int semop(ID,struct sembuf *sops,nsops),第二个参数为系统结构数组,第三个为数组元素个数。

    //函数semop的第二个参数struct sembuf *sops :解释这个结构体
    struct sembuf {
            unsigned short sem_num;    //no:信号量编号
            short sem_op;    //对信号量的操作,若sem_op>0,则信号量+sem_op;若sem_op<0,则信号量-sem_op;若sem_op=0,表示判断信号量是否=0。
            short sem_flg;    //建议选择SEM_UNDO(进程退出时,执行 信号量解除(undo) 操作);IPC_NOWAIT(指定操作若还未完成,进程也不等待,立即返回-1)
    };    

    直接控制信号量信息:int semctl(ID,semnum,cmd,。。。),该函数最多只有4个参数:

    1. semnum为信号量编号,若为设置为0,则表示整个信号量的集合(此时第四个参数无效)。
    2. 如果对整个信号量集合控制,则须#include <ipc.h>,此时cmd的含义与msgctl相同,有如下值:
      IPC_RMID:删除整个信号量集合
      IPC_SET:设置ipc_perm参数
      IPC_STAT:获取ipc_perm参数
      IPC_INFO:获取系统信息 
    3. 若对单个。。。(今后再议)

    网络中,主机间数据交互机制:套接字(socket)、RPC。

  • 相关阅读:
    jdbc在项目中的应用
    第五次作业-springmvc对表单的获取
    jquery的文档处理(部分)
    测试报告怎么写合理
    WebService的简单应用
    hdu--5078--orz
    hdu--5074--dp
    hdu--5108--数论
    hdu--5072--容斥原理
    hdu--3853--概率dp
  • 原文地址:https://www.cnblogs.com/quanxi/p/6116579.html
Copyright © 2011-2022 走看看