Linux期末总结

课后简单题以及程序设计题

简单说明软连接和硬链接文件的区别

  • 创建链接文件的命令为ln,该命令默认创建硬链接文件,若与选项-n搭配使用,则会创建软链接文件。软链接文件是一个新文件,系统会为软链接文件分配inode;软链接文件中存储的内容为其所链接对象的路径。硬链接文件与其所链接的文件共用inode,创建硬链接文件时,该文件的文件名作为一条记录添加到其路径名中目录的dentry中,同时其链接对象的硬链接数会加1。

程序与进程的区别

  • 、程序是“死”的,进程是“活”的,程序是指编译好的二进制文件,它存放在磁盘上,不占用系统资源,是具体的;而进程存在于内存中,占用系统资源,是抽象的。当一次程序执行结束之后,进程随之消失,进程所用的资源被系统回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	pid_t pid;
	pid = fork();
	if (pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if (pid > 0)
	{
		printf("parent process:pid");
	}
	else if (pid == 0)
	{
		printf("child process\n");
		execlp("cat", "-b", "exec.c", NULL);
		perror("error exec\n");
		printf("child process\n");
	}
	return 0;
}

简单说明linux系统信号的处理方式

信号递达进程后才可能被处理,信号的处理方式有三种:忽略、捕捉和执行默认动作。大多数信号都可以被忽略,但9号信号SIGKILL和19号信号SIGSTOP是超级用户杀死进程的可靠方法,不能被忽略,也不能被捕获。

说明kill函数pid不同取值的含义

参数pid的取值可分为4种情况,每种取值代表的含义如下:①pid > 0,则发送信号sig给进程号为pid的进程;②pid = 0,则发送信号sig给当前进程所属组中的所有进程;③pid = -1,则发送信号sig给除1号进程与当前进程的所有进程;④pid < -1,则发送信号sig给属于进程组-pid的所有进程。

简述使用消息队列实现进程间通信的步骤

使用消息队列实现进程间通信的步骤如下:①创建消息队列;②发送消息到消息队列;③从消息队列中读取数据;④删除消息队列。

列举出Linux,中常用的进程通信机制,并对每种机制进行简单说明

Linux系统中常用的进程通信机制有:管道、消息队列、信号量和共享内存。其中管道分为匿名管道和命名管道,匿名管道可用于具有亲缘关系进程间的通信,命名管道克服了此限制,可实现任意进程间的通信。消息队列的本质是一个存储消息的链表,有权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息,消息队列也可实现任意进程间的通信,且克服了管道只能承载无格式字节流以及缓冲区大小受限等缺点。共享内存机制将同一块物理地址映射到不同进程的虚拟地址空间,使得多个进程可访问同一块内存空间,效率最高。信号量主要用于实现进程同步,常常与共享内存一起使用,避免多个进程因争夺共享资源出现错误。

简述线程和进程的区别

1、进程有自己独立的地址空间,多个线程共享进程的地址空间;进程是分配资源的基本单位,线程是处理器调度的基本单位;进程是程序的一次执行过程,线程是进程的一个执行单元;进程和线程均可并发执行,但线程占据的空间更小,耗费的资源更少。

su与sudo的区别

线程.pptx

综合编程题


image.png

  • 所有的系统函数都应该检查返回值,设置error函数
int linux1()
{   int i,fd,result;
    char buf[1000];
    close(1);
    fd=open("nfile",O_RDWR|O_CREAT,0644);
    //lseek(fd,1000,SEEK_SET);
    if(fd<0)
    {   perror("open error\n");
        return -1;
    }
    lseek(fd,1000,SEEK_SET);
    write(fd," ",1);
    char n='a';
    for(i=0;i<26;i++)
    {   printf(n+" ");
        n++;
        sleep(1);
    }
    //write(fd,n,1);
    write(fd,buf,1000);
    close(fd);
    return 0;}

文件系统操作

一、作业

image.png

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
//相对路径(在同一文件目录下)
int main(){
    int fd=open("a.txt",O_RDWR);
    if(fd==-1){//注意这里是两个等于号!!!!!!
        perror("file open error\n");
        exit(1);
    }
    else
        printf("fd=%d",fd);
    
}
//命令行参数
int main(int argc,char *argv[]){
    int fd=open(argv[1],O_RDWR);
    if(fd==-1){
        perror("file opne error\n");
    }
     else
        printf("fd=%d",fd);
 //绝对路径,不再同一文件目录下   
int main(){
    int fd=open("/home/kali/Desktop/test/b.txt",O_RDWR);
    if(fd==-1){
        perror("file open error\n");
        exit(1);
    } else
        printf("fd=%d",fd);

cp

image.png

lseek
  • 返回值是以起始位置为标准向后的偏移程度。
  • 文件的读写使用同一偏移量。
  • 写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。lseek读取的文件大小总是相对文件头部而言。
  • 用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号.末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。
  • 使用 truncate 函数,直接拓展文件。 int ret = truncate(“dict.cp”, 250);

image.png

lseek 示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾
代码如下:
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main(){
int fd,n;
char msg[]={"it's atest for lseek"};
char ch;
fd=open("lseek.txt",O_RDWR);
if(fd==-1){
perror("open error");
exit(1);
}
write(fd,msg,strlen(msg));//将参数msg所指的内存写msg数组的长度到文件fd中
lseek(fd,0,SEEK_SET);
/*while((n = read(fd, &ch, 1))){
if(n < 0){
perror("read error");
exit(1);
}
write(STDOUT_FILENO, &ch, n); 
}//将文件内容按字节读出,写出到屏幕 }
close(fd);
 return 0; 
}*/

len=lseek(fd,0,SEEK_END);//获取文件长度;
while(lseek(fd,0,SEEK_CUR)!=len)
{
read(fd,msgy7,1024);
    printf("%s\n",msg);   
}
close(fd);
return 0;
}
  • “open函数里的文件名或路径名都要加引号!”。
  • 拓展文件的大小下面使用lseek将fcntl.c原本的698字节填充为800字节,差值102字节.


image.png

权限

image.png

  • 例如:输入umask命令后,得出的是0002,umask是八进制,真正的值为002。
  • umask ,mode=775,777,结果为775。
  • umsk,mode=511,775,结果为511
  • 两者相与。

open().close().write().read()

read
  • 函数定义:ssize_t read(int fd, void * buf, size_t count)
  • 函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
  • 返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。另外,以下情况返回值小于count.
  • 读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有50个字节而请求读100个字节,则read返回50,下次read将返回0。
write
  • 函数定义:ssize_t write (int fd, const void * buf, size_t count);
  • 函数说明:write()会把参数buf所指的内存写入count个字节到所指的文件fd内。
  • 返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
  • write()函数返回值一般无0,只有当如下情况发生时才会返回0:write(fd, buf, count)中第三参数为0,此时write()什么也不做,只返回0。

inode

image.png

进程

  • ps aux 返回结果里,第二列是进程 id
  • ps ajx --> pid ppid gid sid
  • exec函数族,,,函数通常用来调用系统程序。如 ls、date、cp、cat 命令。
  • exec函数一旦调用成功,即执行新的程序,不返回。只有失败才返回,错误值-1,所以通常我们

直接在 exec 函数调用后直接调用 perror(), 和 exit(),无需 if 判断。
image.png

image.png

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
	pid_t pid = fork();//调用fork创建子进程
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if(pid>0) {
		printf("parents process,pid=%d,ppid=%d\n",getpid(),getppid());

	}
	else if (pid == 0) {
		printf("child process,pid=%d.ppid=%d",getpid(),getppid());

	}
	return 0;
}

image.png

#include <stdio.h>
#include <stdlib.h>
#icnlude < unidtd.h>
int main() {
	pid_t pid;
    int i;
	for ( i = 0; i < 5; i++) {
		pid = fork();
		if(pid==0)
			break;
	}
	if (pid == -1) {
		perror("fork error");
		exit(1);
		}
	else if (pid > 0) {
		printf("parent process:pid=%d\n", getpid());
	}
	else if (pid == 0) {
		printf("i am child=%d,pid=%d\n",i+1,getpid());
	
	}
	return 0;
	}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
	pid_t pid = fork();//调用fork创建子进程
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if(pid>0) {
		printf("parents process,pid=%d,ppid=%d\n",getpid(),getppid());

	}
	else if (pid == 0) {
		printf("child process,pid=%d.ppid=%d",getpid(),getppid());
        char *arg[]={"-a","-l","test_work.c",NULL};//定义指针数组要加星号
        execvp("ls",arg);
        perror("error exrc\n");
	}
	return 0;
}

image.png
image.png

孤儿进程与僵尸进程
  • 孤儿进程
    • 父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。
  • 僵尸进程
    • 子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。
    • kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。

子进程终止时,子进程残留资源 PCB 存放于内核中,PCB 记录了进程结束原因,进程回收就是回
收 PCB。回收僵尸进程,得 kill 它的父进程,让孤儿院去回收它。

  • 僵尸进程会占据内存空间,要尽量避免僵尸进程的产生,使用wait和waitpid()可以有效避免僵尸进程
  • 若僵尸进程已产生,解决僵尸进程的办法就是终止其父进程,僵尸进程的父进程被终止后,就会变成孤儿进程,会被init接受,init会不断调用wait函数获取子进程状态,收集已退出的子进程发送的状态信息。孤儿进程永远不会变为僵尸进程。
孤儿进程
  1. 孤儿进程的创建
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>

int main()
{
    pid_t pid = fork();
    if(pid == 0)//子进程
    {
        sleep(1);//目的是让父进程先于子进程执行完毕
        printf("son process start\n");
        printf("my pid is %d,my ppid is %d\n",getpid(),getppid());
        printf("son process end\n");
    }
    else
    {
        printf("father process start\n");
        printf("my pid is %d\n",getpid());  
        printf("father process end\n");
    }
    return  0;
}
  1. 孤儿进程的解决

僵尸进程
  1. 僵尸进程的创建

子进程先于父进程退出,且父进程没有对子进程资源进行回收

#include<unistd.h>
#include<sys/types.h>

int main()
{
    pid_t pid = fork();//创建子进程
    if(pid == 0)
    {
        printf("my pid is %d\n",getpid());
        printf("my ppid is %d\n",getppid());
    }
    else
    {
        while(1)
        {
            ;//死循环,阻塞  不处理已经退出的子进程,致使子进程称为僵尸进程
        }
    }
    return 0;
}

**

  1. 僵尸进程的解决
  • 子进程后于父进程退出,变为孤儿进程,有init进程接管子进程,释放其资源
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
 
int main()
{
 
    pid_t res = fork();
    if (res == -1)
    {
        printf("Creat process failed!\n");
        return -1;
    }
 
    else if (res == 0)
    {
        printf("I'm child process,PID = %d,PPID = %d\n", getpid(), getppid());
        while (1)
        {
            sleep(1);
        }
    }
 else
    {
        printf("I'm father process,PID = %d,PPID = %d\n", getpid(), getppid());
    }
 
    exit(0);
}
  • wait()函数
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);//目的是让子进程先于父进程结束
        /*wait函数是阻塞函数,会等到子进程结束回收资源*/
        pid_t dpid = wait(NULL);//对子进程的退出状态不关心
        printf("the died son process pid is %d\n",dpid);
    }
    return 0;
}
1.获取正常的退出状态
#include<sys/types/h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);
        int status = 0;//初始化为0
        pid_t dpid = wait(&status);
        if(WIFEXITED(status))
        {
            printf("exit value:%d\n",WEXITSTATUS(status));
        }
        printf("the died son process pid is %d\n",dpid);
    }
    return 10;//返回值为10  
}

2.获取异常的退出状态
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>

int main()
{
    pid_t pd = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("my pid is %d\n",getpid());
            sleep(2);//控制执行速度不要太快
        }
        else
        {
            sleep(1);
            int status = 0;//初始化为0
            wait(&status);
            if(WIFSIGNALED(status))
            {
                printf("exit by signal : %d\n",WTEEMSIG(status));
            }
        }
    }
    return 0;
}

wait(),waitpid():回收内核空间资源,8

等待子进程结束

  • 调用wait()函数的父进程,如果其所有的子进程都还在运行,则父进程会阻塞。并阻塞等待任意一个子进程结束,回收该子进程的内核资源,如果父进程没有任何子进程,则会出错。
  • waitpid()函数并不等待其在调用之后的第一个终止子进程,他又若干个选项,可以控制它所等待的进程。

waitpid()的第三个参数为等待选项,可以设置为0,亦可为WNOHANG,(不阻塞等待),当此时没有进程退出,即返回0,而不会使阻塞。WUNTRACED:报告状态信息。

  • wait()是随机回收子进程,waitpid是指定回收子进程。

信号

  • 信号抵达进程后才可能被处理,信号的处理方式有三种,忽略,捕获和执行默认动作。

image.png
image.png

在程序中设置计时器,使进程在指定秒数后终止运行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
alarm(1);
while(1){
printf("process will finish\n");
}
return 0;
}

image.png

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(){

pid_t pid;
    pid=fork();
if(pid<-1){
    perror("fork error");
    exit(1);
}
    else if(pid>0){
    while(1){
    printf("parent pid =%d,ppid=%d\n",getpid(),getppid());
    }
}
else if(pid==0){
    sleep(1);
    printf("child pid=%d,ppid=%d\\n",getpid(),getppid());
    kill(getppid(),SIGKILL);
    }

return 0;
}

image.png
image.png

image.png

signal

  • 注册一个信号捕捉函数

sigaction函数

  • 注册一个信号捕捉函数,捕捉不是由这个函数完成的!

进程间通信

image.png
image.png
image.png

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#incldue <sys/types.h>
#incldue <sys/wait.h>
int main(){
    int fd[2];
    int ret=pipe(fd);
    if(ret==-1){
        perror("pipe");
        exit(1);
    }
    pid_t pid=fork();
    if(pid>0){
        close(fd[0]);
        char *p="hello\n";
        write(fd[1],p,strlen(p)+1);
        close(fd[1]);
        wait(NULL);
    }
    else if(pid==0){
        close(fd[1]);
        char buf[64]={0};
        ret=read(fd[0],buf,sizeof(buf));
        close(fd[0]);
        write(STDOUT_FILENO,buf,ret);
    }
    return 0;
}

mmap

线程

image.png
image.png

线程概述:

线程是最小的执行资源单位,进程是最小的分配资源单位。
创建线程
当主线程创建好后,tfn函数会自动被回调
image.png
程序改进:加sleep(1),给子线程执行时间
image.png

#include <stido.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg){
printf("tfn-pid=%d,tid=%lu\n",getpid(),pthread_self());
    return (void *)0;
}
int main(){
    pthread_t tid;
    printf("main--pid=%d,tid=%lu\n",getpid(),pthread_self());
    int ret=pthread_create(&tid,NULL,tfn,NULL);
    
    tid要用取地址运算符
        
    if(ret!=0){
    fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
        
    }
    sleep(1);
    return 0;
}

线程与共享

主子线程间共享全局变量,即线程间共享全局变量

  1. 进程:

进程拥有独立的地址空间,当使用fork()函数创建出新进程后,若其中一个进程要对fork()之前的数据进行修改,进程中会依据“写时复制”原则,先复制一份该数据到子进程的地址空间,再修改数据,因此即便是全局变量,在进程间也是不共享的。

  1. 线程

但由于线程间共享地址空间,因此在一个线程中对全局区的数据进行修改,其它线程中访问到的也是修改后的数据。

线程终止
  • 在一个时间段内,程序被挂起时,可以被取消的一个时间点。(APUE363页有详细的取消点函数)

也就是说,当线程出现 block(阻塞) 时,这个被阻塞的地方就是可被取消的地方。
更通俗的来说:就是线程A执行过程中,如果遇到其他线程B执行cancel函数,

  • 所谓“取消点”就是一个函数,例如sleep(),当线程体运行到sleep()的时候,就可以响应cancel了。
一. 填空题: 1. 在Linux系统中,以 文件 方式访问设备 。 2. Linux内核引导时,从文件 /etc/fstab 中读取要加载的文件系统。 3. Linux文件系统中每个文件用 i节点 来标识。 .... 82. 增加一个用户的命令是:adduser 或useradd 。 83 进行字符串查找,使用grep命令。 84. 使用 * 每次匹配若干个字符。 85. /sbin 目录用来存放系统管理员使用的管理程序。 二.单项选择题: 1. 下面的网络协议中,面向连接的的协议是: A 。 A 传输控制协议 B 用户数据报协议 C 网际协议 D 网际控制报文协议 2. 在/etc/fstab文件中指定的文件系统加载参数中, D 参数一般用于CD-ROM等移动设备。 ... 113.不是shell具有的功能和特点的是 C 。 A 管道 B 输入输出重定向 C 执行后台进程 D 处理程序命令 114.下列对shell变量FRUIT操作,正确的是: C 。 A 为变量赋值:$FRUIT=apple B 显示变量的值:fruit=apple C 显示变量的值:echo $FRUIT D 判断变量是否有值:[ -f “$FRUIT” ] 三.简答题: 1.简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程。 参考答案: Linux通过i节点表将文件的逻辑结构和物理结构进行转换。 i节点是一个64字节长的表,表中包含了文件的相关信息,其中有文件的大小、文件所有者、文件的存取许可方式以及文件的类型等重要信息。在i节点表中最重要的内容是磁盘地址表。在磁盘地址表中有13个块号,文件将以块号在磁盘地址表中出现的顺序依次读取相应的块。Linux文件系统通过把i节点和文件名进行连接,当需要读取该文件时,文件系统在当前目录表中查找该文件名对应的项,由此得到该文件相........ root@xxx:#crontab prgx;在每日早晨8:00之前开机后即可自动启动crontab。 6.设计一个shell程序,在每月第一天备份并压缩/etc目录的所有内容,存放在/root/bak目录里,且文件名为如下形式yymmdd_etc,yy为年,mm为月,dd为日。Shell程序fileback存放在/usr/bin目录下。 参考答案: (1)编写shell程序fileback: #!/bin/sh DIRNAME=`ls /root | grep bak` if [ -z "$DIRNAME" ] ; then mkdir /root/bak cd /root/bak fi YY=`date +%y` MM=`date +%m` DD=`date +%d` BACKETC=$YY$MM$DD_etc.tar.gz tar zcvf $BACKETC /etc echo "fileback finished!" (2)编写任务定时器: echo "0 0 1 * * /bin/sh /usr/bin/fileback" >; /root/etcbakcron crontab /root/etcbakcron 或使用crontab -e 命令添加定时任务: 0 1 * * * /bin/sh /usr/bin/fileback 7.有一普通用户想在每周日凌晨零点零分定期备份/user/backup到/tmp目录下,该用户应如何做? 参考答案:(1)第一种方法: 用户应使用crontab –e 命令创建crontab文件。格式如下: 0 0 * * sun cp –r /user/backup /tmp (2)第二种方法: 用户先在自己目录下新建文件file,文件内容如下: 0 * * sun cp –r /user/backup /tmp 然后执行 crontab file 使生效。 8.设计一个Shell程序,在/userdata目录下建立50个目录,即user1~user50,并设置每个目录的权限,其中其他用户的权限为:读;文件所有者的权限为:读、写、执行;文件所有者所在组的权限为:读、执行。 参考答案: 建立程序 Pro16如下: #!/bin/sh i=1 while [ i -le 50 ] do if [ -d /userdata ];then mkdir -p /userdata/user$i chmod 754 /userdata/user$i echo "user$i" let "i = i + 1" (或i=$(($i+1)) else mkdir /userdata mkdir -p /userdata/user$i chmod 754 /userdata/user$i echo "user$i" let "i = i + 1" (或i=$(($i+1)) fi done
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值