存放在磁盘上的指令和数据的有序集合(文件)
静态的
执行一个程序所分配的资源的总称
进程就是程序的一次执行过程
动态的,包括创建、调度、执行和消亡
进程包括代码、用户数据和 系统数据(进程控制块、cpu寄存器的值、堆栈)
进程标识PID
进程用户
进程状态、优先级
文件描述符表
交互进程:在shell下启动。以在前台运行,也可以在后台运行。
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行。
守护进程:和终端无关,一直在后台运行。
运行态:进程正在运行或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源 – 分为可中断和不可中断两种状态
停止态:进程被终止,收到信号后可以继续运行
死亡态:已终止的进程,但是pcb没有被释放(僵尸进程)
ps 查看系统进程快照 ps -ef | grep ps aux(显示进程当前状态)
top 查看进程动态信息 top
/proc 查看进程详细信息
pid_t fork(void);
以下代码在linux中运行:
#include
#include
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
if(pid == 0)
{
printf("child process; my pid is %d\n", getpid());
}
else
{
printf("parent process; my pid is %d\n", getpid());
}
}
子进程继承了父进程内容
父子进程有独立的地址空间,互不影响
若父进程先结束
若子进程先结束
子进程从何处开始运行? 从fork()之后语句开始执行。
父子进程谁先执行? 不确定,由内核调度决定
父进程能否多次调用fork()? 子进程呢? 都可以
#include
#include
void exit(int status);
void _exit(int status); // 丢弃缓冲区
结束当前进程,并且将status 返回
exit结束进程时会刷新(流)缓冲区
以下代码在linux中运行:
#include
#include
#include
int main()
{
printf("aaa");
_exit(0);
printf("bbbb");
}
调用exec函数族执行某个程序
进程当前内容被指定程序替换
实现让父子进程执行不同的程序
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
成功时执行置顶程序;失败时候返回EOF
path 执行程序名称,包含路径
arg… 传递给执行程序的参数列表
file 执行程序名称,在PATH中查找
#include
#include
#include
int main()
{
if(execl("/bin/ls", "ls", "-al", "/etc", NULL) < 0)
{
perror("execl");
}
}
#include
#include
#include
int main()
{
if(execlp("ls", "ls", "-al", "/etc", NULL) < 0)
{
perror("execlp");
}
}
int execv(const char *path, char *const arg[]);
int execvp(const char *file, char *const arg);
成功时执行置顶程序;失败时候返回EOF
arg… 封装成指针数组的形式
#include
#include
#include
int main()
{
char *argv[] = { "ls", "-al", "/etc", NULL};
if(execv("/bin/ls",argv) < 0)
{
perror("execv");
}
if(execvp("ls",argv) < 0)
{
perror("execvp");
}
}
int system(const char *command);
pid_t wait(int *status);
成功时候返回进程好;失败时候返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,则哪个先结束先回收哪个子进程
status 置顶保存子进程返回值和结束方式的地址
status 为NULL标志直接释放子进程的PCB
nclude <stdio.h>
#include
#include
#include
#include
int main()
{
int status;
pid_t pid;
if((pid = fork()) < 0)
{
perror("fork");
return -1;
}
if(pid == 0)
{
sleep(1);
exit(2);
}
else
{
wait(&status);
printf("%x\n", status);
}
}
子进程通过exit/_exit/return返回某个值(0-255);
父进程调用wait(&status)回收
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0;失败时返回EOF
pid可用于指定回收哪个子进程或者任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式 0或者 WNOHANG
waitpid(pid, &status, 0);
waitpid(pid, &status, WNOHANG);
waitpid(-1, &status, 0);
waitpid(-1, &status, WNOHANG);
守护进程(Daemon)是linux三种进程类型之一
通常在系统启动时运行,系统关闭时结束
Linux系统中大量使用,很多服务程序以守护进程形式运行
特点
if(fork>0)
{
exit(0);
}
if(setsid() < 0)
{
exit(-1);
}
chdir("/");
chdir("/tmp");
if(umask(0) < 0)
{
exit(-1);
}
for(int i = 0; i < getdtablesize(), i++)
{
close(i);
}
#include
#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
FILE *fp;
time_t t;
if((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
if(pid > 0)
{
exit(0);
}
setsid();
umask(0);
chdir("/tmp");
for(int i = 0; i < getdtablesize(); i++)
{
close(i);
}
if((fp = fopen("time.log","a+")) == NULL)
{
perror("fopen");
exit(-1);
}
while(1)
{
time(&t);
fprintf(fp, "%s", ctime(&t));
fflush(fp);
sleep(1);
}
}
进程:
进程有独立的地址空间
Linux为每个进程创建task_struct
每个进程都参与内核调度,互不影响
线程:
进程在切换时候系统开销比较大
很多操作系统引入了轻量级LWP
同一个进程中的线程共享相同的地址空间
Linux不区分进程、线程
特点:
通常线程是指共享相同地址空间的多个任务
使用线程的好处
一个进程中的线程共享以下的资源
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
void pthread_exit(void *retval);
#include
#include
#include
#include
#include
#include
#include
#include
#include
char message[32] = "hello world";
void* thread_func(void *arg);
int main()
{
pthread_t a;
void *result;
if(pthread_create(&a, NULL, thread_func, NULL) != 0)
{
perror("error");
return -1;
}
pthread_join(a, &result);
printf("result is %s\n", result);
printf("message is %s\n", message);
}
void* thread_func(void *arg)
{
sleep(1);
strcpy(message, "marked by thread");
pthread_exit("aaa");
}
线程共享统一进程的地址空间
优点: 线程间通信很容易,通过全局变量交换数据
缺点:多个线程访问共享数据时需要同步或者互斥机制
同步指的是多个任务按照约定的先后顺序相互配合完成一件事情
信号量
信号量来决定线程是继续运行还是阻塞
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
posix中定义了两类信号量
pthread库常用的信号量操作函数如下:
#include
int sem_init(sem_t *sem, int pshared, ussigned int value);
int sem_wait(sem_t *sem); // p操作
int sem_post(sem_t *sem); // v操作
int sem_init(sem_t *sem, int pshared, unsigned int val);
成功时候返回0, 失败时候返回EOF
sem 指向要初始化的信号量对象
pshared 0 - 线程间 1 - 进程间
int sem_wait(sem_t *sem); // p操作
int sem_post(sem_t *sem); // v操作
成功时候返回0, 失败时候返回EOF
sem 指向要操作的信号量对象
信号量代码示例:
#include
#include
#include
#include
#include
#include
#include
#include
#include
char buf[32];
sem_t sem;
void* function(void *arg);
int main()
{
pthread_t a_thread;
if(sem_init(&sem, 0, 0) < 0)
{
perror("sem_init");
return -1;
}
if(pthread_create(&a_thread, NULL, function, NULL) < 0)
{
printf("failed to pthread_create");
return -1;
}
printf("input quit to exit");
do
{
fgets(buf , 32, stdin);
sem_post(&sem);
}while(strncmp(buf, "quit", 4) !=0 );
return 0;
}
void* function(void *arg)
{
while(1)
{
sem_wait(&sem);
printf("you enter %d cahraters\n", strlen(buf));
fflush(stdout);
}
}
使用两个信号量做到数据严格同步:
#include
#include
#include
#include
#include
#include
#include
#include
#include
char buf[32];
sem_t sem_r, sem_w;
void* function(void *arg);
int main()
{
pthread_t a_thread;
if(sem_init(&sem_r, 0, 0) < 0)
{
perror("sem_init");
return -1;
}
if(sem_init(&sem_w, 0, 1) < 0)
{
perror("sem_init");
return -1;
}
if(pthread_create(&a_thread, NULL, function, NULL) < 0)
{
printf("failed to pthread_create");
return -1;
}
printf("input quit to exit");
do
{
sem_wait(&sem_w);
fgets(buf , 32, stdin);
sem_post(&sem_r);
}while(strncmp(buf, "quit", 4) !=0 );
return 0;
}
void* function(void *arg)
{
while(1)
{
sem_wait(&sem_r);
printf("you enter %d cahraters\n", strlen(buf));
fflush(stdout);
sem_post(&sem_w);
}
}
临界资源
一次只允许一个任务(进程、线程)访问的共享资源
临界区
访问临界区的代码
互斥机制
mutex互斥锁
任务在访问临界资源前申请锁,访问完之后释放锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_init:
成功时候返回0,失败时候返回错误码
mutex 指向要初始化的互斥锁对象
attr 互斥锁属性,NULL表示缺省属性
pthread_mutex_lock:
成功时候返回0,失败时候返回错误码
mutex 指向要初始化的互斥锁对象
如果无法获得锁,任务阻塞
pthread_mutex_unlock:
成功时候返回0,失败时候返回错误码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
#include
#include
#include
#include
#include
#include
#include
#include
#include
unsigned int count, value1, value2;
pthread_mutex_t lock;
void* function(void *arg);
#define LOCKM_
int main()
{
pthread_t a_thread;
if(pthread_mutex_init(&lock, NULL) != 0)
{
perror("pthread_mutex_init");
return -1;
}
if(pthread_create(&a_thread, NULL, function, NULL) != 0)
{
perror("pthread_create");
return -1;
}
while(1)
{
count++;
#ifdef LOCKM_
pthread_mutex_lock(&lock);
#endif
value1 = count;
value2 = count;
#ifdef LOCKM_
pthread_mutex_unlock(&lock);
#endif
}
return 0;
}
void* function(void *arg)
{
while(1)
{
#ifdef LOCKM_
pthread_mutex_lock(&lock);
#endif
if(value1 != value2)
{
printf("value1 = %d , value2 = %d\n", value1, value2);
fflush(stdout);
usleep(1000);
}
#ifdef LOCKM_
pthread_mutex_unlock(&lock);
#endif
}
}
早期UNIX进程间通信方式
无名管道(pipe)
有名管道(fifo)
信号(signal)
System V IPC
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)
套接字(socket)
int pipe(int pfd[2]);
pipe:
成功时候返回0,失败时候返回EOF
pfd包含两个元素的整形数组,用于保存文件描述符
pfd[0]用于读管道,pfd[1]用于写管道
#include
#include
#include
#include
int main()
{
pid_t pid1, pid2;
char buf[32];
int pfd[2];
if(pipe(pfd) < 0)
{
perror("pipe");
return -1;
}
if((pid1 = fork()) <0)
{
perror("fork");
return -1;
}
else if(pid1 == 0)
{
strcpy(buf, "im process1");
write(pfd[1], buf, 32);
exit(0);
}
else
{
if((pid2 == fork()) < 0)
{
perror("fork2");
return -1;
}
else if(pid2 == 0)
{
sleep(1);
strcpy(buf, "im process2");
write(pfd[1], buf, 32);
}
else
{
wait(NULL);
read(pfd[0], buf, 32);
printf("%s\n", buf);
wait(NULL);
read(pfd[0], buf, 32);
printf("%s\n", buf);
}
}
return 0;
}
写端存在
有数据 – read返回实际读取的字节数量
无数据 – 进程读阻塞
写端不存在
有数据 – read返回实际读取的字节数量
无数据 – read返回0
读端存在
有空间 – write返回实际写入的字节数
无空间 – 空间不足时候不保证原子操作(有多少写多少,剩余等有空间再写),进程写阻塞
读端不存在不允许写入
管道断裂
对应管道文件,可用于任意进程之间通信
打开管道时候可以指定读写方式
通过文件I/O操作,内容存放在内存中
int mkfifo(const char *path, mode_t mode);
mkfifo:
成功时候返回0,失败返回EOF
path 创建管道文件路径
mode 管道文件权限,如0666
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
Linux对早期unix信号机制进行了扩展
进程对信号有不同的响应方式
缺省方式
忽略信号
捕捉信号
信号名 | 含义 | 默认操作 |
---|---|---|
SIGHUP | 该信号在用户终端关闭时候产生,通常是发送给和该终端关联的会话内所有的进程 | 终止 |
SIGINT | 该信号在用户键入INTR制度时候产生,内核发送此信号到当前终端的所有前台进程(ctrl+c) | 终止 |
SIGQUIT | 该信号和sigint类似,但是由于quit字符来产生(ctrl+\) | 终止 |
SIGLL | 该信号在一个进程企图执行一条非法指令时候产生 | 终止 |
SIGSEV | 该信号在非法访问内存时候产生,如野指针、缓冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代表管道断裂 | 终止 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停继承,并且不能被捕捉和忽略 | 暂停进程 |
SIGTSTP | 该信号用于暂停继承,用户可以键入SUSP字符发出这个信号(ctrl+z) | 暂停进程 |
SIGCONT | 该信号让进程进入运行态 | 继续运行 |
SIGALRM | 该信号用于通知进程定时器时间已到 | 终止 |
SIGUSR1/2 | 该信号保留给用户程序使用 | 终止 |
int kill(pid_t pid, int sig);
int raise(int sig);
int alarm(unsigned int seconds); // 创建一个定时器,如果为0取消当前定时器
int pause(void); // 进程会一直阻塞,知道被信号终端
void (*signal(int signo, void(*handker)(int)))(int);
包括共享内存,消息队列和信号灯集
每个IPC对象有唯一的ID
IPC对象创建后一直存在,知道被显式的删除
每一个IPC对象有一个关联的key
key_t ftok(const char *path, int proj_id);
ftok:
成功时候返回key,失败返回EOF
path是存在可访问的文件路径
proj_id是用于生成key的数字,不能是0
#include
#include
#include
#include
#include
int main()
{
key_t key;
if((key=ftok(".", 'a')) == -1)
{
perror("ftok");
return -1;
}
}
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据拷贝
共享内存在内核空间创建,可以被进程映射到用户空间访问,使用灵活
由于多个进程可以同时访问共享内存,因此需要同步和互斥机制配合使用
使用步骤
// 1. 创建/打开共享内存
int shmget(kety_t key, int size, int shmflg);
// 2. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 3. 读写共享内存
...
// 4. 撤销共享内存映射
int shmdt(void *shmaddr);
// 5. 设置共享内存, 删除共享内存对象
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmget:
key 和共享内存关联的key, IPC_PRIVATE或者ftok生成
shmflg 共享内存标志位 IPC_CREATE | 0666
示例代码(不能直接运行)
// 创建私有
int main()
{
int shmid;
if((shmid = shmget(IPC_PRIVATE, 512, 0666) < 0)
{
perror("shmget");
return -1;
}
}
// 创建关联key的
int main()
{
key_t key;
int shmid;
if((key = ftok('.', 'm') == -1)
{
perror("ftok");
return -1;
}
if((shmid = shmget(key, 1024, IPC_CREEATE|0666) < 0)
{
perror("shmget");
return -1;
}
}
shmat:
shmid 要映射的共享内存ID
shmaddr 映射后的地址,NULL表示由系统自动映射
shmflg 标志位, 0表示可读可写,SHM_RDONLY表示只读
示例代码(不能直接运行)
char *addr;
int shmid;
...
if(addr = (char*)shmat(shmid, NULL, 0) == (cahr *)-1);
{
perror("shmat");
return -1;
}
fgets(addr , N , stdin);
shmdt:
不适用共享内存时候应该撤销映射关系
进程结束时候,操作系统会自动撤销
shmctl:
shmid 要操作的共享内存ID
cmd 要执行的操作 IPC_STAT(获取属性) IPC_SET(设置属性) IPC_RMID(删除ID)
struct shmid_ds *buf 表示共享内存属性
每块共享内存大小有限制
共享内存删除的时间点
使用步骤:
#include
#include
// 1. 打开/创建见消息队列
int msgget(key_t key, int msgflg);
// 2. 向消息队列发送消息
int msgsnd(int msgid, const void *msgp, size_t size, int msgflg);
// 3. 从消息队列接收消息
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);
// 4. 控制消息队列
int msgctl(int msgid, int cmd, struct msqid_df *buf);
msgget:
key 和消息队列关联的key IPC_PRIVATE或ftok
msgflg 标志位IPC_CREATE | 0666
示例代码(不能直接运行)
int main()
{
int msgid;
key_t key;
if((key = ftok(".", "q")) == -1)
{
perror("ftok");
return -1;
}
if((msgid = msgget(key, IPC_CREATE|0666)) < 0)
{
perror("msgget");
return -1;
}
return 0;
}
msgsnd:
msgid 消息队列ID
msgp 消息缓冲区地址
size 发送的消息长度(正文长度)
msgflg 标志位0或者IPC_NOWAIT
示例代码(不能直接运行)
typedef struct
{
long mtype;
char mtext[64];
}MSG
#define LEN (sizeof(MSG) - sizeof(long))
int main()
{
MSG buf;
buf.mtype = 100;
fgets(buf.mtext, 64, stdin);
msgsnd(msgid, &buf, LEN, 0);
...
return 0;
}
msgrcv:
msgid 消息队列id
msgp 消息缓冲区地址
size 接收的消息长度(正文长度)
msgtype 指定接收的消息类型
msgflg 标志位0或者IPC_NOWAIT
示例代码(不能直接运行)
typedef struct
{
long mtype;
char mtext[64];
}MSG
#define LEN (sizeof(MSG) - sizeof(long))
int main()
{
MSG buf;
buf.mtype = 100;
fgets(buf.mtext, 64, stdin);
if(msgrcv(msgid, &buf, LEN, 100,0) < 0)
{
perror("msgrcv");
return -1;
}
...
return 0;
}
msgctl:
msgid 消息队列ID
cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID
struct msqid_df*buf 表示消息队列属性地址
(消息被删除会立刻删除)
信号灯又叫信号量,用于线程或者进程同步或者互斥的机制
信号灯类型
posix 无名信号灯
posix 有名信号灯
信号灯的含义 计数信号灯
system v 信号灯
使用步骤
// 1.打开/创建信号灯
int semget(key_t key, int nsems, int semflg);
// 2. 信号灯初始化
int semctl(int semid, int semnum. int cmd, ...)
// 3. PV操作
int semop(int semid, struct sembuf *sops, size_t nsops);
// 4. 删除信号灯 semctl
semget:
key 和信号灯关联的key IPC_PRIVATE或ftok
nsems 集合中包括的信号灯的个数
semflg 标志位IPC_CREATE | 0666 IPC_EXCL
semctl:
semid 信号灯的ID
semnum 要操作的信号灯的编号
cmd 执行的操作 SETVAL IPC_RMID
union semun 取决于cmd
semop:
semid 要操作的信号灯集
sops 描述对信号灯操作的结构体
nsops 要操作的信号灯个数
struct sembuf
{
short semnum; // 信号灯编号
short sem_op; // -1 p操作 // 1 v操作
short sem_flg; // 0 IPC_NOWAIT
}