进程

  1. 进程基础
1.1 概念
程序:编译好的可执行文件,存放在磁盘上的指令和数据的有序集合(文件),程序是静态的,没有任何执行的概念
进程:一个独立的可调度任务,是执行一个程序锁分配的资源的总称,是程序的一次执行过程,是动态的,包括创建、调度、执行和消亡
1.2 特点
1. 系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。
2. CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作

关于进程和线程,你到底了解多少?_子进程

关于进程和线程,你到底了解多少?_信号量_02编辑

关于进程和线程,你到底了解多少?_信号量_03

关于进程和线程,你到底了解多少?_共享内存_04编辑

1.3 进程段
Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
1.4 进程分类
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
1.5 进程状态
1)运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。
2)睡眠态(等待态):
可中断睡眠态(TASK_INTERRUPTIBLE)S:处于等待状态中的进asdasdasdas程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:该状态的进程只能用wake_up()函数唤醒。
3)暂停态(TASK_STOPPED):T
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
4)死亡态:进程结束 X
5)僵尸态(TASK_ZOMBIE):Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
1.6 进程状态切换
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。

关于进程和线程,你到底了解多少?_信号量_05

关于进程和线程,你到底了解多少?_子进程_06编辑

关于进程和线程,你到底了解多少?_子进程_07

关于进程和线程,你到底了解多少?_信号量_08编辑

  1. 函数
2.1 创建进程fork
pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno

特点:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。

4)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)

总结fork:
1. fork之前的代码被复制,但不会被重新执行一遍;fork之后的代码会被复制,并且分别执行一遍。
2. fork之后两个进程就独立了,子进程拷贝了父进程的所有代码,内存空间独立
3. fork之前打开的文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针

fork>0 父进程
fork==0 父进程
fork<0 进程开辟失败
2.2 回收进程wait waitpid
pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
        失败:-1
        
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0     指定子进程进程号
         =-1   任意子进程
         =0    等待其组ID等于调用进程的组ID的任一子进程
         <-1   等待其组ID等于pid的绝对值的任一子进程
    status:子进程退出状态
    options:0:阻塞
        	WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
      当使用选项WNOHANG且没有子进程结束时:0
      出错:-1

关于进程和线程,你到底了解多少?_信号量_09

关于进程和线程,你到底了解多少?_子进程_10编辑

2.3 退出进程exit _exit
void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回。
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
    通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束

关于进程和线程,你到底了解多少?_共享内存_11

关于进程和线程,你到底了解多少?_子进程_12编辑

2.4 获取进程号 getpid getppid
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号

关于进程和线程,你到底了解多少?_共享内存_13

关于进程和线程,你到底了解多少?_子进程_14编辑

关于进程和线程,你到底了解多少?_共享内存_15

关于进程和线程,你到底了解多少?_信号量_16编辑

2.5  

return: 关键字,在子函数中返回到函数调用的位置,并不结束进程(函数的退出)

exit :函数,不管在子函数还是主函数都会结束进程(进程的退出)

  1. exec 函数族
int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

关于进程和线程,你到底了解多少?_信号量_17

关于进程和线程,你到底了解多少?_子进程_18编辑

  1. 守护进程
4.1 特点
守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
4.2 步骤
1.创建子进程,父进程退出,让子进程编程孤儿进程。
2.给子进程开辟会话组,让他成为会话组组长。
3.改变进程运行路径为根目录
4.重设文件掩码
5.关闭文件描述符

关于进程和线程,你到底了解多少?_子进程_19

关于进程和线程,你到底了解多少?_子进程_20编辑

用守护进程实现输入Hello功能

关于进程和线程,你到底了解多少?_共享内存_21

关于进程和线程,你到底了解多少?_共享内存_22编辑

  1. 同步
5.1 概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
5.2 同步机制 信号量
      5.2.1 基础
通过信号量实现线程间同步。
信号量:通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待
信号量代表某一类资源,其值表示系统中该资源的数量,信号量值>0,表示有资源可以用,可以申请到资源,继续执行程序,信号量值<=0,表示没有资源可以用,无法申请到资源,阻塞。
信号量是一个受保护的变量,只能通过三种操作来访问:初始化sem_init、P操作(申请资源)sem_wait、  V操作(释放资源)sem_post
      5.2.2 函数
int  sem_init(sem_t *sem,  int pshared,  unsigned int value)  
功能:初始化信号量   
参数:sem:初始化的信号量对象
    pshared:信号量共享的范围(0: 线程间使用   非0:1进程间使用)fmnu    `
    value:信号量初值
返回值:成功 0
      失败 -1
int  sem_wait(sem_t *sem)  
功能:申请资源  P操作 
参数:sem:信号量对象
返回值:成功 0
      失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int  sem_post(sem_t *sem)   
功能:释放资源  V操作      
参数:sem:信号量对象
返回值:成功 0
      失败 -1
注:释放一次信号量的值加1,函数不阻塞

关于进程和线程,你到底了解多少?_共享内存_23

关于进程和线程,你到底了解多少?_信号量_24编辑

  1. 线程
6.1 创建线程 pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                    void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识
            attr:线程属性, NULL:代表设置默认属性
            start_routine:函数名:代表线程函数
            arg:用来给前面函数传参
返回值:成功:0
              失败:错误码
6.2回收线程pthread_join
int  pthread_join(pthread_t thread,  void **value_ptr) 
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
        value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
       失败:errno
6.3 退出线程 pthread_exit
int  pthread_exit(void *value_ptr) 
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值(任意类型)
返回值:成功 : 0
        失败:errno
练习:通过线程实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。

关于进程和线程,你到底了解多少?_共享内存_25

关于进程和线程,你到底了解多少?_共享内存_26编辑

8.1 概念
临界资源:一次仅允许一个进程/线程所使用的资源
临界区:指的是一个访问共享资源的程序片段

斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
8.2 函数
int  pthread_mutex_init(pthread_mutex_t  *mutex, pthread_mutexattr_t *attr)  
功能:初始化互斥锁  
参数:mutex:互斥锁
    attr:  互斥锁属性  //  NULL表示缺省属性
返回值:成功 0
      失败 -1
int  pthread_mutex_lock(pthread_mutex_t *mutex)   
功能:申请互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int  pthread_mutex_unlock(pthread_mutex_t *mutex)   
功能:释放互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1
int  pthread_mutex_destroy(pthread_mutex_t  *mutex)  
功能:销毁互斥锁     
参数:mutex:互斥锁
8.3 条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数:cond:是一个指向结构pthread_cond_t的指针
    restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
返回值:成功:0 失败:非0

int pthread_cond_wait(pthread_cond_t *restrict cond,    pthread_mutex_t *restrict mutex);
功能:等待信号的产生
参数:restrict cond:要等待的条件
     restrict mutex:对应的锁
返回值:成功:0,失败:不为0
注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。

int pthread_cond_signal(pthread_cond_t *cond);
功能:给条件变量发送信号
参数:cond:条件变量值
返回值:成功:0,失败:非0
注:必须等待pthread_cond_wait函数先执行,再产生条件才可以
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0

int pthread_cond_broadcast(pthread_cond_t *cond)
功能:给全部变量发送信号

关于进程和线程,你到底了解多少?_共享内存_27

关于进程和线程,你到底了解多少?_子进程_28编辑

关于进程和线程,你到底了解多少?_子进程_29

关于进程和线程,你到底了解多少?_信号量_30编辑

  1. 进程之间的通信机制
8.1进程间通信方式
1. 早期通信:
无名管道(pipe)、有名管道(fifo)、信号(sem);
2. system V IPC:
共享内存(share memory)、消息队列(message queue)、信号灯(semaphore)
3.BSD:
套接字(socket)
8.2无名管道
8.2.1 原理图
8.2.2 无名管道的特点
1. 只能用于有亲缘关系的进程之间通信
2. 半双工通信,具有固定的读端和后端
单工 只能单向的传输信息
半双工 可以双向传输信息,但是一次只能进行读或者写一个操作
全双工 可以双向传输信息
3. 无名管道可以看作特殊的文件,对于无名管道的读写可以使用IO函数read、write.
4. 无名管道是基于文件描述符进行通信,当一个无名管道建立,它会创建两个文件描述符,f[0]是读端,f[1]是写端,只会返回两个且固定的。
8.2.3 函数
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0
      失败 -1
注意:
1. 当管道中无数据时,读会阻塞;管道中无数据,将写端关闭,读操作立即返回
2. 当管道中写满(64k)数据时,写阻塞,一旦有4k空间,写继续,直到写满为止
3. 将读端关闭,继续写数据,会导致管道破裂,进程会收到内核发送过来的SIGPIPE信号(Broken pipe)
8.3 有名管道
8.3.1 特点
1)有名管道可以使互不相关的两个进程互相通信。
2)有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。
3)通过文件IO来操作有名管道
4)有名管道遵循先进先出规则
5)不支持lseek()操作
8.3.2 mkfifo
int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)
8.3.3 注意事项
只写方式,写阻塞(open),直到另一个进程将读打开
只读方式,读阻塞(open),直到另一个进程将写打开
可读可写,若管道中无数据,读阻塞
8.4 信号量
8.4.1 概念
1)信号是在软件层次上对中断机制的一种模拟,是异步通信方式 2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。 3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
8.4.2 信号的响应方式
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作 。
8.4.3 信号的种类
2)SIGINT:结束进程,对应快捷方式ctrl+c
3)SIGQUIT:退出信号,对应快捷方式ctrl+\
9)SIGKILL:结束进程,不能被忽略不能被捕捉
15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
17)SIGCHLD:子进程状态改变时给父进程发的信号
19)SIGSTOP:暂停进程,不能被忽略不能被捕捉
20)SIGTSTP:暂停信号,对应快捷方式ctrl+z
26)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程
  1. 共享内存
9.1 特点
1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
4)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

在物理内存申请出来了一段内存用来给大家一起使用,通过特定的ID(由key申请)去访问这个内存空间,用的是将私有内存直接映射到共享内存
9.2 步骤
1. 创建key值 ftok
2. 创建或打开共享内存 shmget
3. 映射共享内存到用户空间 shmat
4. 撤销映射 shmdt
5. 删除共享内存 shmctl
9.3 函数
      9.3.1 ftok
key_t ftok(const char *pathname, int proj_id);
功能:产生一个独一无二的key值
参数:
    Pathname:已经存在的可访问文件的名字
    Proj_id:一个字符(因为只用低8位)
返回值:成功:key值
      失败:-1
       9.3.2 shmget
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
    key  键值
    size   共享内存的大小
    shmflg   IPC_CREAT|IPC_EXCL(判错)|0666
返回值:成功   shmid
      出错    -1
       9.3.3 shmat
void  *shmat(int  shmid,const  void  *shmaddr,int  shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:
    shmid   共享内存的id号
    shmaddr   一般为NULL,表示由系统自动完成映射
              如果不为NULL,那么由用户指定
    shmflg:SHM_RDONLY就是对该共享内存只进行读操作
                0     可读可写
返回值:成功:完成映射后的地址,
      失败:-1的地址
          9.3.4 shmdt
int shmdt(const void *shmaddr);
功能:取消映射
参数:要取消的地址
返回值:成功0  
      失败的-1
           9.3.5 shmctl
int  shmctl(int  shmid,int  cmd,struct  shmid_ds   *buf);
功能:(删除共享内存),对共享内存进行各种操作
参数:
    shmid   共享内存的id号
    cmd     IPC_STAT 获得shmid属性信息,存放在第三参数
            IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
            IPC_RMID:删除共享内存,此时第三个参数为NULL即可
返回:成功0 
     失败-1
用法:shmctl(shmid,IPC_RMID,NULL);

关于进程和线程,你到底了解多少?_信号量_31

关于进程和线程,你到底了解多少?_共享内存_32编辑

9.4 命令
ipcs -m: 查看系统中的共享内存
ipcrm -m shmid:删除共享内存

10 消息队列

10.1 特点
1.消息队列是IPC对象的一种
2.消息队列由消息队列ID来唯一标识
3.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
4.消息队列可以按照类型来发送(添加)/接收(读取)消息
10.2 步骤
1)产生key值 ftok
2)创建或打开消息队列msgget
3)添加消息:按照类型把消息添加到已打开的消息队列末尾msgsnd
4)读取消息:可以按照类型把消息从消息队列中取走msgrcv
5)删除消息队列msgctl
10.3 函数
        10.3.1 msgget
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数:  key值
       flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
       失败:-1
        10.3.2 msgsnd
int msgsnd(int msqid, const void *msgp, size_t size, int flag); 
功能:添加消息
参数:msqid:消息队列的ID
      msgp:指向消息的指针。常用消息结构msgbuf如下:
          struct msgbuf{
            long mtype;          //消息类型
            char mtext[N]};   //消息正文
   size:发送的消息正文的字节数
   flag:IPC_NOWAIT消息没有发送完成函数也会立即返回    
         0:直到发送完成函数才返回
返回值:成功:0
      失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
        10.3.3 msgrcv
int msgrcv(int msgid,  void* msgp,  size_t  size,  long msgtype,  int  flag);
功能:读取消息
参数:msgid:消息队列的ID
     msgp:存放读取消息的空间
     size:接受的消息正文的字节数
    msgtype:0:接收消息队列中第一个消息。
            大于0:接收消息队列中第一个类型为msgtyp的消息.
            小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
     flag:0:若无消息函数会一直阻塞
        IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
      失败:-1
        10.3.4 msgctl
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
     cmd:
        IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
        IPC_SET:设置消息队列的属性。这个值取自buf参数。
        IPC_RMID:从系统中删除消息队列。
     buf:消息队列缓冲区
返回值:成功:0
      失败:-1
用法:msgctl(msgid, IPC_RMID, NULL
  1. 信号灯集  
  1. 概念
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。
  1. 步骤
步骤
1. 创建key值 ftok
2. 创建或打开信号灯集 semget
3. 初始化信号灯 semctl
4. PV操作: semop
5. 删除信号灯集 semctl
  1. 函数
1. semget

int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
    nsems:信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限,通常为IPC_CREAT |IPC_EXCL|0666
返回值:成功:信号灯集ID
       失败:-1

2. semctl

int semctl ( int semid, int semnum,  int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
    semnum: 要操作的集合中的信号灯编号
     cmd:
        GETVAL:获取信号灯的值,返回值是获得值
        SETVAL:设置信号灯的值,需要用到第四个参数:共用体
        IPC_RMID:从系统中删除信号灯集合
返回值:成功 0
      失败 -1
用法:初始化:
union semun{
    int val;
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值


3. semop

int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
     opsptr:操作方式
     nops:  要操作的信号灯的个数 1个
返回值:成功 :0
      失败:-1
struct sembuf {
   short  sem_num; // 要操作的信号灯的编号
   short  sem_op;  //    0 :  等待,直到信号灯的值变成0
                   //   1  :       
                   //   -1 :  申请资源,P操作                    
    short  sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
用法:
申请资源 P操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);
释放资源 V操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);
  1. 命令
ipcs -s:查看信号灯集
ipcrm -s semid:删除信号灯集