https://www.processon.com/view/570c8af9e4b05489d1ed8316

https://www.zybuluo.com/phper/note/595507

程序,进程,线程的概念

  • 程序就是指令和代码的集合。进程是动态的,有一定生命周期
进程的产生方式
  • 复制父进程的环境配置
  • 内核中建立进程结构
  • 插入到进程列表
  • 分配资源给这个进程
  • 复制父进程的内存映射信息
  • 管理文件描述符和链接点
  • 通知父进程
进程的终止方式

exit()函数定义在stdlib.h中,而_exit()定义在unistd.h中。exit()和_exit()都用于正常终止一个函数。但_exit()直接是一个sys_exit系统调用,而exit()则通常是普通函数库中的一个函数。它会先执行一些清除操作,例如调用执行各终止处理函数、关闭所有标准IO等,然后调用sys_exit。

  • main返回
  • 调用exit
  • 调用_exit
  • 调用abort
  • 由一个信号终止
进程间的通信
  • 管道:利用内核在两个进程之间建立通道。
  • 共享内存:将内存中的一段地址在多个进程中共享
  • 消息队列:在内核中建立一个链表
进程间的同步
  • 消息队列
  • 信号量

进程产生的方式

  • pid_t getpid(void);返回当前正在运行的进程的进程号
  • pid_t getppid(void);返回当前正在运行的进程的父进程的进程号
进程复制fork

fork函数以父进程为蓝本复制一个进程,当调用fork的时候。进程开始分叉。fork执行了一次,但是返回了两次,在父进程和子进程中分别返回了不同值。父进程返回的是子进程的ID号,子进程中返回的是0.

system方式

int system(const char *command)
相当于python里面的system.call,相当于执行外部命令

exec函数

exec函数会用新的进程代替原有的进程。系统会从新的进程运行,新进程的PID值会与原来进程的PID值相同。exec不会有返回,因为exec已经占用了当前的资源,记住execve函数就可以了
int execve(const char *filename, char *const argv[], char *const envp[])
文件名,参数列表, 系统变量

init

所有的进程都是由init进程初始化出来。ubuntu采用systemd来进行(最早是init).
查看进程树可以用pstree命令,可以看到init进程是没有兄弟进程的.
有关systemd可以看这篇文章http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

进程间的通信和同步

多个进程间的通信机制为IPC(Inter-Process Communication).

半双工管道(|)

ls -l|grep *c,进程创建管道,每次创建两个文件描述符。其中一个对管道进行写操作,另一个对管道进行读操作
同样,可以用pipe函数建立一个管道,这样就可以让父进程和子进程进行通信
int pipe(int fds[2]);

命名管道

与传统的无名的shell管道不同,命名管道利用了文件系统。 使用 mkfifo()或 mknod()创建命名管道。 两个进程可以通过管道的名字打开、读写管道。实际上是以设备特殊文件的形式存在的。
管道文件的前缀为p

1
2
3
yudun1989@ubuntu:~/lnp/ch4$ mkfifo /tmp/namedfifo
yudun1989@ubuntu:~/lnp/ch4$ ls -l /tmp/namedfifo
prw-rw-r-- 1 yudun1989 yudun1989 0 Dec 8 16:26 /tmp/namedfifo

在c中想要创建命名管道,使用mkfifo()
int mkfifo(const char *pathname, mode_t mode)
命名管道和文件读写基本类似,但是在打开的时候设置了读权限的时候,读进程会一直阻塞。一直到其他进程打开并写入程序。
同理,当没有读取的时候,写入也会阻塞。如果不希望阻塞,就需要设置O_NONBLOCK标识,以关闭默认的阻塞动作。

消息队列

消息队列是内核地址空间中的内部链表。通过linux内核在进程中传递内容。每个消息队列可以用IPC消息唯一标识符进行标识。不同消息队列之间是相对独立的,每个消息队列中的消息,又构成一个独立的链表。

  • 内核消息结构

    1
    2
    3
    4
    5
    struct msgmbuf {
    long mtype;
    char mtext[1];
    long length;
    }

    mtype标识消息类型,mtext其实多长都可以,在收发的时候内核不会对mtext进行转换。只是消息的大小存在内部限制。/linux/msg.h中限制为#define MSGMAX 8192

  • 结构msgid_ds:每个消息队列都有这个结构与之关联。里面分别含有消息队列最后一个消息时间戳,消息数目, 能容纳的最大字节数目,消息进程的pid等

  • 结构ipc_perm:包含IPC对象的许可权限信息如用户UID,建立者UID,权限序列号等
  • ftok,相当于计算出一个IPC的key
  • msgget获得消息
  • msgsnd发送消息
  • msgrecv接收消息
  • msgctl消息控制函数
信号量

信号量是一个计数器,用来控制多个进程共享的资源所进行的访问。 生产者和消费者模型是信号量的典型使用。

  • semget新建信号量函数
  • semop信号量操作函数

以下为P, V操作

1
2
3
4
5
6
7
8
9
int Sem_P(sem_t semid){
struct sembuf sops={0, +1, IPC_NOWAIT};
return (semop(semid, &sops, 1));
}

int Sem_V(sem_t semid){
struct sembuf sops={0, -1, IPC_NOWAIT};
return (semop(semid, &sops, 1));
}

  • semctl 控制信号量参数,包括获取semid_ds结构,返回集合中信号量的值,内核中删除该集合等
共享内存:几个进程之间共享一块内存区域
  • 创建共享内存shmget.int shmget(key_t key, size_t size, int shmflg)
  • 获得共享内存地址shmat,void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 删除共享内存shmdt, int shmdt(const void *shmaddr)
  • 共享内存控制函数shmctl, int shmctl(int shmid, int cmd, struct shmid_ds *buf)
信号

linux中定义了一系列信号,信号可以由内核产生,也可以由系统中其他进程产生。只要有足够权限。

kill -l可以列出所有信号。
signal用于截取系统的信号,对此信号瓜姐用户自己的处理函数。

sighandler_t signal(int signum, sighandler_t handler)
也可以手动向程序发送kill信号和raise, raise是给当前pid发送一个信号

linux下的线程

多线程编程实例

Linux下的多线程遵循POSIX标准,叫pthread,可以使用man thread查看解释,需要包含头文件pthread.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <pthread.h>

static int run = 1;
static int retvalue;
void *start_routine(void *arg){
int *running = arg;
printf("子线程初始完毕,传入参数为%d\n", *running);
while(*running){
printf("子线程正在运行\n");
usleep(1);
}
printf("子线程退出\n");
retvalue = 8;
pthread_exit((void *)&retvalue);
}

int main(void){
pthread_t pt;
int ret = -1;
int times = 3;
int i=0;
int *ret_join = NULL;
ret = pthread_create(&pt, NULL, (void *)start_routine, &run);
if(ret != 0){
printf("建立线程失败\n");
return 1;
}
usleep(1);
for(;i<times;i++){
printf("主线程打印\n");
usleep(1);
}
run = 0;
pthread_join(pt, (void *)&ret_join);
printf("线程返回值为:%d\n", *ret_join);
return 0;
}

因为包含了pthread,所以编译的时候需要添加 -lthread参数