Linux系统编程——进程
一、进程
1、进程:进程是一个程序执行的过程,会去分配内存资源,cpu的调度。
进程在内存上,程序在硬盘上。
2、pcb(进程控制块)是一个结构体。包括如下内容:
.PID是进程标识符
.当前工作路径(chdir切换路径)
.umask
.进程打开的文件列表
.信号相关设置(处理异步io)
.用户id,组id
进程资源上限
.ulimit -a:显示进程资源上限,[进程能开的文件:1024,进程栈的大小:8M]
3、进程和程序的区别
程序:静态,存储在硬盘中代码,数据的集合
进程:动态,程序执行的过程,包括进程的创建、调度、消亡
- 程序是永存,进程是暂时的
- 进程有程序状态的变化,程序没有
- 进程可以并发,程序无并发(同一时刻,多个任务共同运行)
- 进程与进程会存在竞争计算机的资源(race condition)
- 一个程序可以运行多次,变成多个进程,一个进程可以运行一个或多个程序
调度: 调度是操作系统分配计算资源(如CPU时间)给多个任务的过程
并发:同一时刻,多个任务共同运行
并行:同时执行多个任务,多cpu
4、进程的内存分布
程序,属于文件,都在硬盘上,通过编译./a.out,分配内存
堆区程序员申请,程序员释放
ELF是linux的可执行程序,PE是Windows的可执行程序
32为操作系统(2^32=4G)
代码段---数据段---堆区---share---栈区(8M)——进程的空间占3G
内核——占1G
5、虚拟地址(./a.out),物理地址
MMU:内存管理单元
虚拟内存---mmu和页表--->物理内存
6、进程的分类
交互式进程
批处理进程 shell脚本(一行一行的)
守护进程
7、进程的作用:并发
并发:单个CPU通过快速切换,实现多个程序"看似同时"运行,本质是时间分片。
并行:多个CPU/核心物理同时执行不同程序。
8、进程的状态
基本操作系统:--./a.out-->就绪态--调度-->运行态--条件不满足-->阻塞态--满足-->就绪态
Liunx系统:0(运行态、就绪态),睡眠态(可中断、不可中断),僵死态,暂停态。
9、进程的调度
宏观并行,微观串行
10、调度算法
实时系统(stm32,rt_thread ucos )、分时系统(Linux、Windows)
上下文切换:进程1到进程2,3GB的内存(包括寄存器,pc,内存指针,资源信息等)会保存到硬盘
先来先服务:谁先来,就先给它彻底走完(实时)
短任务优先:谁走的快的谁就先走(实时)
优先级:分配优先级(0-20)(分时)
时间片轮询:同优先级,cpu保证同优先级的分配时间相对公平(分时)
11、进程相关命令
ps aux:查看进程相关信息
top:根据CPU占用率查看进程相关信息
kill/killal:发送一个信号
kill(ps获取id号,再kill)
killall(killall -9 进程名)
12、进程重点函数
12.1 fork()
pid_t fork()
一次调用,会返回两次
子进程和父进程运行的顺序不确定
变量不共享
子进程复制父进程的0到3g空间和父进程内核中的PCB,但id号不同
功能:
通过该函数可以从当前进程中克隆一个同名新进程。
克隆的进程称为子进程,原有的进程称为父进程。
子进程是父进程的完全拷贝。
子进程的执行过程是从fork函数之后执行。
子进程与父进程具有相同的代码逻辑。
返回值:int 类型的数字。
在父进程中:成功 返回值是子进程的pid号 >0
失败 返回-1;
在子进程中:成功 返回值 0
失败 无
父子关系是写时复制(cow):不变不会复制,变了才复制???
应用场合:
1)一个进程希望复制自己,使父子进程同时执行同的代码段。网络服务中会比较多见。
2)一个进程需要执行一个不同的程序。fork+exec
12.2 getpid()
pid_t getpid(void);
功能:
获得调用该函数进程的pid
参数:
缺省
返回值:
进程的pid
12.3 getppid()
pid_t getppid(void);
功能:
获得调用该函数进程的父进程pid号
参数:
缺省
返回值:
返回父进程id号
13、考点
fork() && fork() || frok()
14、父子进程的关系:
子进程是父进程的副本。子进程获得父进程数据段,堆,栈,正文段共享。
在fork之后,一般情况那个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。(进程间通信)
15、进程的终止
主动退出
1)main 中return
2)exit(): c库函数,会执行io库的清理工作,关闭所有的流,以及所有打开的文件。已经清理函数(atexit)。
3)_exit,_Exit:会关闭所有的已经打开的文件,不执行清理函数。(系统调用的)
4)主线程退出
5)主线程调用pthread_exit
异常终止
6)abort()
7)signal:kill pid
8)最后一个线程被pthread_cancle
16、进程的退出
孤儿进程:父进程先结束,子进程就是孤儿进程。
僵尸进程:进程执行结束但空间未被回收变成僵尸进程
回调函数 atexit
int atexit(void (*function)(void));
功能:注册进程退出前执行的函数
参数:function:函数指针【指向void返回值void参数的函数指针】
返回值:成功返回0 失败返回非0当程序调用exit或者由main函数执行return时,所有用atexit
注册的退出函数,将会由注册时顺序倒序被调用
17、进程空间的回收
pid_t wait(int *status);(父进程中调,只回收一个)
功能:该函数可以阻塞等待任意子进程退出, 并回收该进程的状态,一般用于父进程回收子进程状态。(父回收子,子没有做完,父就阻塞了)
参数:status 进程退出时候的状态
如果不关心其退出状态一般用NULL表示
如果要回收进程退出状态,则用WEXITSTATUS回收。
返回值:成功 回收的子进程pid,失败 -1;
WIFEXITED(status) 是不是正常结束
WEXITSTATUS(status) 使用这个宏去那返回值
WIFSIGNALED(status) 是不是收到了信号而终止的
WTERMSIG(status)如果是信号终止的,那么是几号信号。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{pid_t pid = fork();if(pid>0){printf("father ,id:%d\n",getpid());int status; //退出状态pid_t pid2 = wait(&status);//block 阻塞if(WIFEXITED(status)) //是不是正常结束{ //在退出状态中,取出 退出值 printf("child normal terminal... ,exit val %d\n",WEXITSTATUS(status));}if(WIFSIGNALED(status)) //是不是异常结束,信号导致的{printf("terminal by signal ,signal num:%d\n",WTERMSIG(status));}printf("recycle end... pid2 is %d\n",pid2);}else if (0 == pid){printf("child id:%d\n",getpid());sleep(10);exit(20);}else {perror("fork");return 1;}return 0;
}
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程,(非阻塞:父进程回收子进程,子进程没结束,会循环去找其他进程进行回收)
参数:pid 子进程id号
status 子进程退出时候的状态,如果不关注退出状态用NULL;
options 选项:0 表示回收过程会阻塞等待,那就与wait相等了。
WNOHANG 表示非阻塞模式回收资源。
返回值:成功 返回接收资源的子进程pid 失败 0(子进程没结束),-1(出错)
18、 execute
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。
其实有六种以exec开头的函数,统称exec函数:execl("/bin/ls","-l","-i",NULL);
execlp("ls","-l","-i",NULL);//PATH
execv("/bin/ps", ps_argv);
execvp("ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
minshell小项目
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>void show_prompt()
{char path[512] = {0};getcwd(path, sizeof(path));printf("[%s]$", path);fflush(stdout);
}int main(int argc, char **argv)
{while (1){show_prompt();char cmd_line[256] = {0};// cp 1 2\nfgets(cmd_line, sizeof(cmd_line), stdin);cmd_line[strlen(cmd_line) - 1] = '\0';char *args[4] = {NULL};args[0] = strtok(cmd_line, " ");args[1] = strtok(NULL, " ");args[2] = strtok(NULL, " ");if(NULL == args[0]){continue;}// cd cd /123if (0 == strcmp("cd", args[0])){if (NULL == args[1]){chdir("/home/linux");}else{chdir(args[1]);}continue;}pid_t pid = fork();if (0 == pid){// ll ls -alhF ll 123 ls 123 -alhFif (0 == strcmp("ll", args[0])){if (NULL == args[1]){args[0] = "ls";args[1] = "-alhF";args[2] = NULL;}else{args[0] = "ls";args[2] = "-alhF";args[3] = NULL;}}execvp(args[0], args);exit(1);}else if (pid < 0){perror("fork");exit(1);}wait(NULL);}// system("pause");return 0;
}
19、 system()函数
int system(const char *command); (内置cd和别名ll命令走不了)
功能:使用该函数可以将shell命令直接在代码中执行。
参数:command要执行的shell命令
返回值:成功 0 失败 -1
二、线程
1、概念
优点: 比多进程节省资源,可以共享变量。
概念:线程是轻量级进程,一般是一个进程中的多个任务。
进程是系统中最小的资源分配单位.
线程是系统中最小的执行单位。
2、线程与进程区别:
1、线程属于某个进程
2、当进程运行起来后,默认有一个线程,叫主线程
3、线程与线程,平级,主次之分
4、创建的开销不同:进程 3G。线程8M
5、线程空间是共享的(只有栈区独立的),进程空间独立的
6、稳定性:线程弱,进程强
3、特征
1、共享资源
2、效率高 30%
3、三方库: pthread clone posix
3.1 编写代码头文件: pthread.h
3.2 编译代码加载库: -lpthread library
4、创建多线程
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:该函数可以创建指定的一个线程。
参数:thread 线程id,需要实现定义并由该函数返回。
attr 线程属性,一般是NULL,表示默认属性。
start_routine 指向指针函数的函数指针。本质上是一个函数的名称即可。称为回调函数th,是线程的执行空间。
arg 回调函数的参数,即参数3的指针函数参数。
返回值:成功 0 失败 错误码
pthread_t pthread_self(void); unsigned long int; %lu
功能:获取当前线程的线程id
参数:无
返回值:成功 返回当前线程的线程id 失败 -1;
5、线程的退出
void pthread_exit(void *retval); exit return p;
功能:子线程自行退出
参数: retval 线程退出时候的返回状态,临死遗言。
返回值:无
int pthread_cancel(pthread_t thread);
功能:请求结束一个线程
参数:thread 请求结束一个线程tid
返回值:成功 0 失败 -1;
6、线程的回收(回收的栈区)
int pthread_join(pthread_t thread, void **retval);
功能:通过该函数可以将指定的线程资源回收,该函数具有阻塞等待功能,如果指定的线程没有结束,则回收线程会阻塞。
参数:thread 要回收的子线程tid
retval 要回收的子线程返回值/状态。【ptread_exit(值)】
返回值:成功 0 失败 -1;
7、线程传参:传字符串、传结构体
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>#include <stdlib.h>
typedef struct
{int id;char name[256];
}PER;
void* th(void*arg)
{PER* tmp = (PER*)arg;printf("%d %s\n",tmp->id,tmp->name);return NULL;
}
int main(int argc, char **argv)
{pthread_t tid;PER per;char buf[256]={0};printf("input id to per");fgets(buf,sizeof(buf),stdin);per.id = atoi(buf);printf("input name to per");fgets(per.name,sizeof(per.name),stdin);per.name[strlen(per.name)-1]='\0'; // zhangsan\n zhangsanpthread_create(&tid,NULL,th,&per);pthread_join(tid,NULL);//system("pause");return 0;
}
8、设置分离属性,目的线程消亡,自动回收空间。
9、线程清理函数(两个成对出现的)
void pthread_cleanup_push(void (*routine)(void *), void *arg);
功能:注册一个线程清理函数
参数:routine,线程清理函数的入口
arg,清理函数的参数。
返回值:无
void pthread_cleanup_pop(int execute);
功能:调用清理函数
参数:execute,非0 ,执行清理函数 0 ,不执行清理
返回值:无
三、线程控制
1、线程互斥
互斥 :在多线程中对临界资源的排他性访问。
互斥机制 ===》互斥锁 ===》保证临界资源的访问控制。
1.1 定义互斥锁
pthread_mutex_t mutex;
1.2 初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
1.3 加锁、解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
1.4 销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
练习:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>pthread_mutex_t mutex;
int win=3;
void* th(void* th)
{while (1){pthread_mutex_lock(&mutex);if(win>0){win--;pthread_mutex_unlock(&mutex);printf("Get win...\n");sleep(rand()%3);printf("Release win...\n");pthread_mutex_lock(&mutex);win++;pthread_mutex_unlock(&mutex);break;}else{pthread_mutex_unlock(&mutex);sleep(rand()%3);}}return NULL;
}int main()
{pthread_t tid[10]={0};pthread_mutex_init(&mutex, NULL);for(int i=0;i<10;i++){pthread_create(&tid[i], NULL, th,NULL);}for(int i=0;i<10;i++){pthread_join(tid[i], NULL);}pthread_mutex_destroy(&mutex);return 0;
}
1.5 trylock
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:类似加锁函数效果,唯一区别就是不阻塞。参数:mutex 用来加锁的互斥锁
返回值:成功 0 失败 非零
2、线程同步
同步:有一定先后顺序的对资源的排他性访问,(原因:互斥锁可以控制排他访问但没有次序)
2.1 信号量的定义
sem_t sem
2.2 信号量的初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:将已经定义好的信号量赋值。
参数:sem 要初始化的信号量
pshared = 0 表示线程间使用信号量,!=0 表示进程间使用信号量
value 信号量的初始值,一般无名信号量,都是二值信号量,0 1
0 表示红灯,进程暂停阻塞
1 表示绿灯,进程可以通过执行
返回值:成功 0 失败 -1;
2.3 信号量的PV 操作
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
2.4 信号量的销毁
int sem_destroy(sem_t *sem);
二值信号量
计数信号量
考点:
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
四、进程间通信
(全局变量:进程空间独立,写时复制,???)
三大类:
古老的通信方式(无名管道、有名管道、信号)
IPC对象通信
socket通信
无名管道:只能给有亲缘关系进程通信
有名管道:可以给任意单机进程通信
管道的特性:
1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。
3、管道是特殊文件,读写使用文件IO。
1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2.写端是存在的,读管道,如果管道为空的话,读会阻塞。
3.管道破裂,,读端先关闭,写管道破裂。
4. read 0 ,写端关闭,如果管道没有内容,read 0 ; ???
创建管道 ==》读写管道 ==》关闭管道
无名管道的创建应该在fork之前进行。
有名管道:有文件名称的管道。
创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道 ==》卸载有名管道
open阻塞:
5、进程间通信:信号通信
应用:异步通信(类似中断)
响应(关闭Term、忽略ign、等待wait 、内核转储core、暂停stop、继续cont)
发送端:
重点函数:kill和signal
int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
sig 当前程序要发送的信号编号 《=== kill -l
返回值:成功 0
失败 -1;
unsigned int alarm(unsigned int seconds);SIGALAM
功能:定时由系统给当前进程发送信号
int pause(void);
功能:进程暂停,不再继续执行,除非收到其他信号。
接收端:
每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理
2、忽略处理 9,19
3、自定义处理 9,19 捕获
共享内存 :效率最高的进程间通信方式
key ==》申请对象 ==》映射对象==》读写对象==》撤销映射 ==》删除对象