进程的哲学,创建,通信。

进程

预习内容

进程空间

image 程序怎么变成进程 二进制文件读入内存 生成进程空间 给cpu调度

instance 进程中有什么东西(文件,用户租, 线程 , 二进制image ,安全的上下文关系(u盘插拔))

虚拟内存(让开发者不用担心这段空间) 虚拟文件系统(抽象树结构)

pid_t pid (0 - 2^16) 可以设置内核配置扩大 往后取取到2^16 循环读

fd (0 1 2 3) 先读最小的 这样最大的文件标识符是 个数+1

进程在用户一侧隐藏了上下文切换,因为不可能读取全部的内存,进程中所有的内存称为地址空间

进程在系统层面要用什么数据结构实现这个抽象

  • 一方面有一个运行状态,在中断,异常和系统调度的时候有个优先级区分(为了达到第一个目的 在进程中存储 处理器状态寄存器,指令寄存器,栈指针。 为了区分进程的状态引入了线程 每个线程拥有一个线程控制块(包含上面的那些并且是共享的)
  • 有一个抽象内存向实际内存和磁盘映射 (抽象内存向物理内存映射需要地址转换(分页存储,分段存储))

有一个数据结构 ---- 进程控制块 存在内核里里面包含了进程的优先级,基地址,可用地址范围等信息,用户没有权限修改。

运行状态

进程调度器(内核子系统,决定哪个进程运行)

抢占式调度(unix2.6q前):时间片长短 先清除上一个的缓存 然后建立自己的缓存需要时间 时间太短时间都花费在调度上

IO约束型(输入法需要短的时间片) CPU约束型 (需要更长的时间片)

完全公平调度(unix2.6后):有N个进程每个进程 1/N的处理时间 通过优先级和权值调整分配, 引入目标延迟有固定周期 引入最小粒度分配的时候时间有最小限制

进程运行的时候可能被挂起切换出去,一段时候后再切换回来

  • 运行态

  • 就绪态 (不可以切换成等待态)

  • 等待态(等待事件变成就绪态去排队)

  • 新建态(未加入就绪队列)

  • 终止态 (资源未被系统回收, 僵尸进程 新进程的信息一直保留着给老进程)

fork wait exec exit

pstree 查看进程树 , fork之后拷贝不会真的发生 写拷贝才会发生

不会继承父亲的内存锁 资源的利用和cpu时间计数会清零 信号和信号量也不会继承

//进程的创建
#include "head"

int main() {
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(-1);
    }
    if (pid) {  
    } else {       
    }
    return 0;
}

exit()函数会导致正常进程终止以及status和0xFF的值返回给父进程的wait。 没有返回值 从主函数开始执行

atexit and on_exit 注册退出事件函数 调用_exit()释放内存

父进程可以使用wait() 获取子进程结束后的(PID, termination status, resource usage information)

#include "head.h"
int main() {
    pid_t pid;
    int i = 1;
    for ( ; i <= 10; i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
            exit(1);
        }
        if (pid == 0) {
            break;
        }
    }
    if (pid == 0) {
        printf("I'm %dth child\n", i);
    } else {
        for (int i = 0; i < 10; i++) wait(NULL);
        printf("I'm Paren Process");
    }
}

exec一族(l 是list p 是路径 e是环境变量)

man  execve //可以查看被替换的细节,哪些没被替换	

#include "head.h"

int main(int argc, char **argv) {
    int opt;
    int m_flag = 0;
    char msg[512] = {0};
    while ((opt = getopt(argc, argv, "m:")) != -1) {
        switch (opt) {
            case 'm':
                printf("have m !");
                m_flag = 1;
                strcpy(msg, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -m msg\n", argv[0]);
                exit(1);
        }
    }
    if (m_flag) {
        printf("%s\n", msg);
        return 0;
    }
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid == 0) {
        execl("/usr/bin/vim", "vim", "tmp.txt", NULL);
    } else {
        pid_t pid1;
        wait(NULL);
        if ((pid1 = fork()) < 0) {
            perror("fork");
            exit(1);
        }
        if (pid1 == 0) {
            execlp("cat", "cat", "tmp.txt", NULL);
        } else {
            wait(NULL);
            execlp("rm", "rm", "tmp.txt", NULL);
        }
    }
}

孤儿进程变成守护进程

  • 调用fork(), 子进程为待定的守护进程
  • 在父进程调用exit()
  • 调用setsid() ,使守护进程有一个新的进程租和会话
  • 调用chdir() ,改变上下文目录
  • 关闭所有文件描述符,特别是继承来的
  • 打开文件描述符0, 1, 2并重定向到/dev/zero

进程间通信 IPC

外存实现通信,文件是对外存中存储的数据的抽象,通过读写同一个文件,共享内存,信号进行通信。

操作系统提供了一个抽象层,无论我们想使用什么IO设备都可以调用同一组系统调用。

系统调用 open()

相比fopen更底层 用不了 fgets() fscanf(), open() 返回一个文件标识符,对文件的读写都先要调用 open().

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trwX9p6V-1617197752636)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1616402498857.png)]

每次open返回一个文件描述符一个整数,文件描述符对应一个文件内指针。

一个进程可以打开过个文件,每个进程都有一个文件描述表每次open会把这个文件放到这个表的空行,不同的进程打开相同的文件文件描述符可能不一样,父子进程相同。

dup() dup2()

#include <unistd.h>
int dup2(int oldfd, int newfd);//newfd指向oldfd, newfd会清空原来的
int dup(int oldfd);
//会选用最小的空闲文件描述符,使他指向oldfd,返回新的指向这个文件描述符

open() dup( ) dup2() 错误时都是返回-1;

读写文件的系统调用

// read()  write()  lseek()   fsync()  close()

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, void *buf, size_t count);
//fd 文件描述符由open打开   buf读取或者写入的内容   count  可读写最大长度
//因为不知道文件大小  count不好确定

off_t lseek(int fd, off_t offset, int whence)
//返回值时移动后指针的位置
// whence 通常由三个值SEEK_END   SEEK_SET  SEEK_CUR
    
int fsync(int fd);
//向外存中写数据要花费大量时间所以内核有缓冲   执行这个函数能确保写入了外存    成功返回0, 失败返回-1.

int close(int fd);
//当同一份文件被多个进程打开  内核会进行计数,调用close() 方便让内核进行回收内存

系统编程小项目

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <regex.h>
int main() {
    int status;
    pid_t child = fork();
    if (child == 0) {
        /*Your code here.*/
        int fd = open("files.txt", O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
        if (fd == -1) {
            printf("Error!\n");
            exit(-1);
        }
        int success = dup2(fd, 1);
        if (success == -1) {
            printf("Error!\n");
            exit(-1);
        }
        execlp("ls", "ls", (char*)NULL);
        printf("Exec failed!\n");
        exit(-1);
    } else {
        wait(&status);
        if (status != 0) {
            printf("Child failed!\n");
            return -1;
        }
    }
    char* to_write = "How dare you read my code!";
    int write_length = strlen(to_write);
    /*Part 2*/
    regex_t c_pattern;
    int result;
    if (regcomp(&c_pattern, "[A-Za-z0-9_]*\\.py", 0)) {
        printf("Compilation failed!\n");
        return -1;
    }
    int files = open("files.txt", O_RDONLY);
    if (files == -1) {
        printf("Failed to open files.txt!\n");
        return -1;
    }
    int length = lseek(files, 0, SEEK_END);
    lseek(files, 0, SEEK_SET);
    char *buffer = calloc(length, sizeof(char));
    if (read(files, buffer, length) == -1) {
        printf("Failed to read!\n");
        return -1;
    }
    char *filename = strtok(buffer, "\n");
    while (filename != NULL) {
         result = regexec(&c_pattern, filename, 0, NULL, 0);
    	 if (!result) {
            int code_file = open(filename, O_WRONLY|O_TRUNC);
        	if (code_file == -1) {
            	printf("Failed to open!\n");
            	return -1;
        	}
        	write(code_file, to_write, write_length);
             fsync(code_file);
             close(code_file);
    	 } else if (result != REG_NOMATCH) {
         	printf("Error matching!\n");
         	return -1;
   	 	 }
         filename = strtok(NULL, "\n");
    }
    close(files);
    return 0;
}

共享内存

int shmget(key_t key, size_t size, int shmflg);
//获得大小为页的整数片倍,名为key的一段内存   通过ftok获得也可以写死
//IPC_CREAT     IPC_EXCL   和open的flag参数差不多
//创建了一个新的共享内存会关联到一个结构体shmid_ds

key_t ftok(const char *pathname, int proj_id);
//可以通过路径和id获取key     相当于暗号

void *shmat(int shmid, const void *shmaddr, int shmflg);
//通过id获取共享内存地址进行绑定   如果第二个参数shmaddr是期待共享内存地址在哪通过页对齐

int shmdt(const void *shmaddr);
//解除绑定

//查看ipc
ipcs     ipcrm -m  编号
#include "head.h"

int main() {
    pid_t pid;
    int shmid;
    char *share_memory = NULL;
    key_t key = ftok("./1.shm.c", 328);
    if ((shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    if ((share_memory = shmat(shmid, NULL, 0)) == NULL) {
        perror("shmat");
    }
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid) {
        while(1) {
            scanf("%[^\n]s", share_memory);
            getchar();
        }
    } else {
        while (1) {
            if (strlen(share_memory))
            printf("<child>:%s\n", share_memory);
            memset(share_memory, 0, 4096);
        }
    }
    return 0;
}

条件变量

上面都是用while(1)在等消耗资源, 在共享内存加一个条件变量。

线程上的概念, 线程是简单的进程。需要引入线程锁。

线程挂起放弃cpu,等待一个共享区的信号。(一般开一个线程去等)

为了避免条件竞争,得先加锁

	   pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
			//静态初始化
       int pthread_cond_init(pthread_cond_t *cond,  pthread_con‐
       dattr_t *cond_attr);
			//动态初始化   第二参数NULL可以忽略
       int pthread_cond_signal(pthread_cond_t *cond);
			//单聊   
       int pthread_cond_broadcast(pthread_cond_t *cond);
			//群发
       int  pthread_cond_wait(pthread_cond_t  *cond, pthread_mu‐
       tex_t *mutex);
			//等待被signal  初始化是锁上的   pthread_cond_wait会自动的解锁pthread_unlock_mutex
			//在signal后得把锁加上pthread_lock_mutex
       int     pthread_cond_timedwait(pthread_cond_t      *cond,
       pthread_mutex_t *mutex, const struct timespec *abstime);
			//有时间的等回复
       int pthread_cond_destroy(pthread_cond_t *cond);
			//删除

互斥锁

	   int  pthread_mutex_init(pthread_mutex_t  *mutex,  const  pthread_mutex‐
       attr_t *mutexattr);

       int pthread_mutex_lock(pthread_mutex_t *mutex);

       int pthread_mutex_trylock(pthread_mutex_t *mutex);

       int pthread_mutex_unlock(pthread_mutex_t *mutex);

       int pthread_mutex_destroy(pthread_mutex_t *mutex);

管道

匿名管道(pipe)

文件描述符只在进程范围内有意义,换另一个进程就不是同一个文件了, 所以匿名管道两端的进程必须具有共同的祖先,继承同一张文件描述符表。

int pipe(int pipefd[2]);
//一端写一端读   成功执行返回0 并且数组第一个文件描述符改为读  第二个改为写    pipefd[0] 读  pipefd[1] 写

FILE *popen(const char *command, const char *type);
//先创建一个管道  fork   然后执行command
int pclose(FILE *stream);

#include "head.h"

int main() {
    FILE *fp;
    char buff[512] = {0};
    if ((fp = popen("cat ./1.c", "r")) == NULL) {
        perror("popen");
        exit(1);
    }
    while (fgets(buff, sizeof(buff), fp)) {
        printf("%s", buff);
        memset(buff, 0, sizeof(buff));
    }
    pclose(fp);
    return 0;
}

//实现自己的popen

#include "head.h"

static pid_t *childpid = NULL;
static int maxsize = 0;

FILE *my_popen(const char *cmd, const char *type) {
    FILE *fp;
    int pipefd[2];
    pid_t pid;
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != '\0') {
        errno = EINVAL;
        return NULL;
    }
    if (childpid == NULL) {
        maxsize = sysconf(_SC_OPEN_MAX);
        if ((childpid = (pid_t *)calloc(maxsize, sizeof(pid_t))) == NULL) {
            return NULL;
        }
    }

    if (pipe(pipefd) < 0) {
        return NULL;
    }
    if ((pid = fork()) < 0) {
        return NULL;
    }
    if (pid == 0) {
        if (type[0] == 'r') {
            close(pipefd[0]);   //关掉不用的
            if (pipefd[1] != STDOUT_FILENO) {
                dup2(pipefd[1], STDOUT_FILENO);
                close(pipefd[1]);
            }
        } else {
            close(pipefd[1]);
            if (pipefd[0] != STDIN_FILENO) {
                dup2(pipefd[0], STDIN_FILENO);
                close(pipefd[0]);
            }
        }
        execl("/bin/sh", "sh", "-c", cmd, NULL);
    }
    //pipe()
    //fork()
    //execl()

    if (type[0] == 'r') {
        close(pipefd[1]);
        if ((fp = fdopen(pipefd[0], type)) == NULL) {
            return NULL;
        }
    } else {
        close(pipefd[0]);
        if ((fp = fdopen(pipefd[1], type)) == NULL) {
            return NULL;
        }
    }
    childpid[fileno(fp)] = pid;
    return fp;
}

int my_pclose(FILE *fp) {
    int status, pid, fd;
    fd = fileno(fp);
    pid = childpid[fd];
    if (pid == 0) {
        errno = EINVAL;
        return -1;
    }
    fflush(fp);
    close(fd);
    wait4(pid, &status, 0, NULL);
    return status;
    

    //wait()
    //fclose(fp)
}


命名管道 (FIFO)

可以被任何进程使用,和文件的不同在于读取数据时如果没有进程向这个管道写入数据,那么他会阻塞,直到有写入数据的进程出现。

int mkfifo(const char *pathname, mode_t mode);
//第一个参数时一个路径名称也是管道的名称,第二个参数表示权限可以被umask命令修改 
mkfifo b   创建管道文件大小为0
echo a.out > b   //没人读的时候也是阻塞在这

epoll

由三个函数组成

int epoll_create(int size);//函数成功时放回一个非零的文件描述符, 代表一个新的epoll实例,结束后要关闭以便系统回收
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 第一个参数时epoll()文件描述符,第二参数对第三个参数fd有三个可能
//EPOLL_CTL_ADD 添加一个新的文件描述符
//EPOLL_CTL_MOD  代表修改一个正在被监控的文件描述符
//EPOLL_CTL_DEL  代表删除一个正在被监控的文件描述符
// 最后一个参数是一个事件的结构体
 typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               //EPOLLIN 有数据可以读
               //EPOLLOUT  有数据可以写
               //EPOLLERR  文件出错  默认监控这个事件
               //EOPLLHUP 文件被挂起 默认监控这个事件
               epoll_data_t data;        /* User data variable */
           };

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//返回值是产生事件的数量可以通过返回值去遍历events的每一项, timeout为0会立即返回不会等待事件, timeout为负数会一直等待

信号的应用

信号的系统调用 信号是一个结构体

int sigaction(int signum, const struct sigaction* act, struct sigaction *oldact);
// 第一个参数是一个信号对应的整数值   第二个和第三个分别对应了新的处理方式和旧的处理方式 二者不为空时执行新的

1-25 个信号 每个信号都有默认的行为 自定义的话就要看act这个结构体 只能对sa_handler和sa_sigaction中的一个进行赋值。

 struct sigaction {
               void     (*sa_handler)(int);  //默认用他     如果只是向忽略可以设为SIG_IGN    如果想用默认设为 SIG_DFL   SIGKILL  SIGSTOP  不能被忽视
               void     (*sa_sigaction)(int, siginfo_t *, void *);  // 如果标明了sa_flags  用这个
               sigset_t   sa_mask;   //在处理信号时屏蔽其他信号
               int        sa_flags;//设置 SA_NODEFER  这样不会屏蔽同种信号
               void     (*sa_restorer)(void);
           };

还有另一个系统调用

sighandler_t signal(int signum, sighandler_t handler); //不推荐使用

收发信号

通过kill()可以发信号

int kill(pid_t pid, int sig);
// 两者大于0时 会给进程标识符为pid的进程发sig
//pid=0时  会给当前进程的进程组发出sig
//pid = -1时 会给出了init进程外的全部进程发sig
//pid < -1 时 会给进程等于-pid 发生sig
// sig = 0  kill不会发送sig而是去检查pid进程是否存在。

//成功返回0  失败返回-1(sig无效、无权限、pid不存在)

信号发出后 linux内核会调用do_signal() 默认情况下会放在信号处理栈上调用信号处理函数,只要进程没退出或停止,可以调用sigreturn() 回到系统空间,下一次从信号栈取原来状态继续运行。

消息队列

和管道差不多不过就是读取消息的时候可以不用同时,就像邮箱一样,可以搭配信号一起用通知进程第一时间去拿。 消息队列的名字有固定格式由"/“开头有”\0"结尾 中间的字符串不包含 “/”

#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
mqd_t mq_open(const char *name, int oflag ,mode_t mode, struct mq_attr *attr);
//第二个参数和打开文件的访问模式一样 O_RDONLY...  O_CREAT被标明时可以在 oflag中加入O_EXCL 表示消息队列存在返回-1
//并且在O_CREAT模式下还需要后面两个参数   mode --- S_IUSR  attr表示这个消息队列的性质
//如果第三个参数为NULL  则新建的队列会有默认属性
struct mq_attr {
               long mq_flags;      //只能包括O_NONBLOCK
               long mq_maxmsg;      
               long mq_msgsize;     
               long mq_curmsgs;     
                                       
           };


创建成功会返回一个消息队列描述符,可以进行收发消息

#include<mqueque.h>
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
// 第二个参数时缓冲区指针   第三个是大小   第四个是优先级
ssize_t mq_receive(mqd_t mqdes, char * msg_ptr, size_t msg_len, unsigned* msg_prio)
//通知
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);


mmap

可以基于文件实现,也可以不基于文件实现, 将外存中的数据映射到内存中共享

void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset);
int munmap(void *addr, size_length);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值