Linux系统编程总结

进 程

进程控制

  进程操作

1.fork创建子进程时候:父进程返回子进程pid,子进程返回0,如果没有创建成功返回负数,程序共用,数据和堆栈各自有自己的。

2.vfork创建的是共用程序,数据和堆栈的子进程,父进程要等子进程运行结束再运行。

3.exit属于stdlib库,对应c库中的returnerrno属于errno库。

4.进程id,父进程id,用户id,有效用户id,组id,有效组id都有自己的变量名。可以用setuid设置对应的id号。

5.执行程序exec后后面的程序被执行程序覆盖掉。

6.程序中执行shell命令:调用system函数,所以system实现是创建子进程,子进行调用exec加载shell,让shell执行命令,父进程等待子进程执行完返回结果。

二关系操作

1.wait用于父进程等待子进程执行完成,并可以通过不同的判断和宏来知道子进程返回的状态。

2.waitpid用于等待指定id进程。

3.僵尸进程是由于父进程没有调用wait函数导致的,在服务器中父进程创建子进程相应客户端程序,不能调用wait,调用的话会阻塞,只能相应一个客户端程序,不调用就会产生许多僵尸进程。为了解救这个问题,创建孙子进程相应客户端程序,这样孙子进程可以被init回收。

 

线程

1 Linux中实现线程:轻量级进程,共享代码,数据,不共享堆栈。

2 创建线程:pthead_create(pthread*restrict tidp,const pthread_sttr_t *restrict attr, void*(*start_rtn)(void),void *restrict arg)第一个参数是pthread类型指针,当函数返回时,tidp就指向创建的线程ID;第二个参数一般为NULL;第三个参数是指向线程函数体的指针;第四个参数是传给线程的参数,如何传参,参看mul_canshu.c,向线程传参就是线程利用了进程资源。

      主线程和其他线程运行时间关系,应该是不相关的。

* mul_canshu运行不成功,没有运行创建的线程,但是在thread_access中包含了传参并且运行成功。

3 终止线程

      有三种方式:1)线程自己执行完;2)调用pthread_exit,类似进程的exit函数;3)被其他线程调用kill函数杀死其他线程可以调用pthread_joinpthread_t tid, void **rval_ptr)返回完成线程的信息,第一个参数是完成线程的tid,第二个参数是返回的信息,void*表示任何类型的数据。对于1)、2)返回的是线程函数或者pthread_exit指针指向的区域;对于3)信息是PTHREAD_CANCELED;如果第二个参数设置为NULL,则等待其执行完成。还可以返回各种数据的首地址,在下面就是(也就是说,任何线程结束后都会返回一个地址,不要被其void迷惑)。如果调用pthread_join过程中,指定的线程未完成,调用线程就会阻塞。

  thread_exit.c

4 正确得到线程退出信息的方法

      线程运行结束时,只在Linux内核中保留退出信息内存区域的首地址,并未将退出信息实际保存在内核中,所以,在线程运行结束后保存退出信息的内存区域仍然有效,所以不能将退出信息存储在局部变量中。应该使用动态内存分配或者全局变量。

  thead_exit_state.c

5 kill 一个线程函数

      int pthread_cancel(pthread_t tid)等效于被kill线程自己调用

  pthread_exit(PTHREAD_CANCELED).

  cancel.c

6 线程退出函数

       类似进程的exit函数,有两个pthead_cleanup_pushpthread_cleanup_pop,正常退出时候,不会调用。

7 线程高级操作

8 线程同步:使用互斥量

对应进程的信号量,因为在同一进程中共享资源,为了同步与互斥,设置了互斥量。

 

信号

1 signal产生五种方式:硬件,kill1)函数发送,kill2)命令,键盘终端由用户发送,内核监测到程序条件满足时候。

2 信号的有害影响:重入(链表的插入)

3 信号的处理函数signal,第一个参数是信号,第二个参数第一个参数处理的函数指针,有三种:SIN_IGN,表示忽略;SIN_DFL,由系统默认处理;handler由用户自己定义处理,通常第一个参数系统已经确定信号的类型,只有SIGUSR1SIGUSR2用于应用程序的通信。所以可以利用系统的信号来使用自己的处理函数。默认killcpidSIGKILL)如果没有signalSIGKILLhandler)的话,其实是调用了signalSIGKILLSIN_DFL)。杀死子进程不代表子进程不需要等待了。杀死进程后,不会有任何善后处理。

4 alarm定时发送处理信息,处理函数是alrm_handler,也要注册信号,这个可以用于网络设备的数据包没有及时到达使得读写操作阻塞,设置定时可以在规定时间读写不成功就返回。

还可以和挂起函数pause一起实现sleep函数。

5 信号集与屏蔽信号:信号集sigset_t,每个信号对应一个位,提供了一些信号设定函数,segemptysetsigfillsetsigaddsetsigdelset以及查询某个信号是否设置函数sigismember,记得信号no-1.信号屏蔽函数sigprocmask

*处理未决信号:sigspend

*高级信号注册函数:sigaction

6 函数使用的参数形式是指针,而变量不是指针,就要取变量的地址作参数,本质上就是变量的首地址。

程序未实现:P404P390

 

IPC

1 进程间通信多种方式:管道IPC对象

2 匿名半双工管道的作用限于共同祖先进程间,如父子进程,兄弟进程。pipe fork组合实现父子进程管道通信,注意读写端打开与关闭。数据流只有一个方向。

* vfork父进程等子进程执行完后再执行,fork呢?为什么在匿名管道这里父进程延迟3妙向管道写数据,子进程还能收到,不是应该早就执行完了吗?难道和管道有关。必须完成管道的某项任务?

* 僵尸进程和父进程死没死没有关系,关键看子进程执行完后夫进程有没有wait子进

程,这里没有wait子进程,可是没有僵尸进程产生?

3 匿名半双工管道的标准库函数popenpclose属于stdio.hpopen返回的是文件指针,不能在用低级的whiteread操作,要用fgetspopen调用了forkpipefork子进程调用了exec执行命令后通过管道返回一个文件指针。

* 程序有问题 P425

4 FIFO:也是半双工的。解决了匿名的无关进程通信问题以及存在于文件系统。shell命令:mkfifo可以创建FIFO。使用mkfifo创建fifo文件,第一个参数是文件名,第二个是模式,模式的理解为权限。

creat_fifo最重要的是怎么给主函数赋参数argv,当赋了argv时,argc就会变化。

FIFO其实就是一个用于进程通信的文件,其打开,读写函数openreadwrite均为文件的操作。

应用:客户端和服务端进程通信,多个客户端和服务端共享一个公共接口FIFO,实现客户端向服务端发送请求,可以为每个客户端设置一个独立的FIFO用于回传应答。但是会导致服务端负载过大。

write_fifo.cread_fifo.c

5 POSIX IPC包括种方式:共享内存,信号量,消息队列。IPC对象是基于内核的,和管道不一样,IPC 对象有自己的标示符和一个key

共享内存:shmgetshmcntlshmatshmdt信号量:semgetsemopsemcntl

消息队列:msggetmsgcntlmsgsndmsgrcv

*snd_msgrcv_msg没有成功 P453

 

时间和日历历程

1.time可以获得time_t。还有更精确的timevaltimezone以及timespec

2.o日历结构体tm,可以使用gmtimelocaltime来将time_t转换为tmmktime相反转换。

void * gettime()函数返回的是某个数据类型的指针,不是函数指针;

 

文 件

文件IO

内核有一个文件表,文件表项对应打开的文件。进程中有一个整形数组,数组下标对应打开的文件表项,这些数组下标叫做文件描述符。

2 对文件操作会使文件偏移量发生变化,可以使用lseek更改打开的文件偏移量

3 truncate截短文件,ftruncate截短一个已经打开的文件。

4 文件同步

文件的写操作会由于缓冲的缘故致使输出延时,在一段时间内,导致内文件和外存文件内容不一样。fsync可以保证肯定同步,sync不能保证

5 文件描述符复制dup dup2操作

* 如果一个文件在一个进程中被打开了,

6 文件重定向:一个进程开始运行时候,由三个文件(设备)总是被打开的:标准输入、标准输出、标准出错。重定向的概念就是使用一个文件代替系统默认的标准。

  输入重定向:<filename;输出重定向:>filename例如:./get_max < input.txt > output.txt

7 控制文件

  stat("xx",&statbuf)可以得到文件状态;intfcntl(int filedes, int cmd, ... )第一个参数是文件描述符,第二个是命令,第三个根据第二个来确定。

8 非阻塞文件I/O

  某些低速文件(设备),使得进程读写操作受限制,容易阻塞,所以对于低速文件(设备),要由fcntk设置为非阻塞的打开文件。

9 mmap内存映射和文件缓冲区不是一个概念。缓冲区较小,如遇到大的文件,需要多次与外设交互影响效率。可以不用再使用readwrite来读写文件了。void mmap(void * addr, size_tlen, int prot, int flag, int fides, off_t off)第一个参数是映射首地址,通常为NULL;第二个是长度;第三个是访问权限;第四个是映射区性质(对映射去改动反映不放映到外存上);第五个是要映射文件的文件描述副。int munmap(caddr_t addr. size_t len)

10 映射同步

  int msync(void *addr, size_t len, int flags)第一个参数是映射首地址,第二个是长度,第三个是回写标志

11 更改内存映射权限

   int mprotect(void *addr, size_t len, int PROT)第三个参数是创建内存映射的第三个参数。

* protect没有成功(注意:打开文件时候要是可读可写的,且文件中要有字符)

文件管理:

前面所说的文件是普通文件,并且是打开的文件,下面的文件状态是所有中类的文件,不管其有没有打开。

 

文件管理

1 文件状态

  statconst char * restrict pathname,struct stat * restrict buf)第一个参数是文件路径;第二个是取得的文件状态。fstatint filedes, struct stat * restrictbuf)可以得到打开文件的状态

2 文件类型

  普通文件、目录文件、块特殊文件(带缓冲的设备的抽象)、字符特殊文件(不带缓冲设备的抽象)、命名管道、符号连接、套接字

权限文件进程

   进程的ID有:进程ID,实际用户ID,有效用户ID,实际组ID,有效组ID

   文件的ID有:所有者ID,所有组ID

   设置进程用户ID位和设置进程组ID位作用:我们都能执行这个进程(有效用户是我们),某个用户是某文件所有者,该用户能执行这个进程读写文件,则我们都能使用这个进程读写文件,只要设置用户进程ID位为1

   一个用户要想读写一个不是他的文件,必须要和文件所有者都在进程的有效用户里,同时使用程序将设置(有效)用户ID位置1,这个用户才能使用这个进程读写文件。

   只有根用户有权修改文件所有者,并设置文件的进程用户标志位。

文件权限操作

   权限测试函数:int access(const char * path, int mod).第二个参数是测试模式。测试目前用户对文件是否用某种权限。

   文件权限屏蔽字,为什么要用这个?有些服务器允许客户端在服务器端创建程序,但是不允许创建可以运行的程序(所有者执行位,组用户执行位,其他用户执行位都应为0),并不是所有用户都遵守规矩,使用这个函数可以屏蔽客户端创建文件的执行位,当然也可以屏蔽其他位。(这里可以理解下多用户多任务的意义,多用户可以同时在一台机器上)umask()

运行的maskshell进程的子进程,子进程改变父进程的运行环境(mask)不应该。(shell调用mask时候其实是fork的一个子进程的)

   shell命令使用chmod也可以改变文件权限,chmod也可以是函数在程序中改变文件权限,fchmod可以修改一个打开的文件权限。

5 文件系统结构

  文件由两部分组成:i结点(包含与目录项的硬连接数)和目录项(保存文件名和i结点号) mv命令改文件名其实就是在该目录下创建了一个目录项,并将其指向原来文件的i结点。打开一个文件,则文件描述副就指向文件的i结点,(理解:open打开文件并不是真的从硬盘中开始打开,而是系统内核中已经有的。打开的文件(可以这样看这里的打开,系统中存在该文件的i结点了),那里叫做文件表,然后本进程再从文件表中有文件再open得到文件描述副fd,就可以使用文件了,close也是消除fd,不是i结点)。在进程中open文件后,若该掉了目录项的文件名,其实此时fd已经指向了该文件的i结点,所以读写还是该文件,文件名其实是一个触发器,它并没参与里面的所有执行。

i结点(文件控制块):各种文件的信息以及硬链接数;

(文件)目录项:只有文件名和i结点指针;多个目录项组成目录文件。

文件表:已经被装到内核(内存)的目录文件。

文件描述符:在进程中打开的文件,这些文件已经存在于文件表中。

6 硬连接

  一个目录项只能连接一个文件,一个文件可以连接多个目录项(快捷方式)。创建硬连接函数:int link(const char*existingpath, constchar *newpath).第一个参数是现有路径,第二个参数是新的文件名。删除一个连接函数:intunlink(const char *pathname)

  当一个i结点没有任何的目录项与之连接,文件表就会删除该文件(就是删除i结点),如果在没有删除所有连接之前已经open了文件,还可以继续读写,close后就会删除i结点。

 

目录管理

1 目录本质是一个文件,可以读写,存储的内容是目录下的目录项。

2 目录文件访问权限也有所有者,组和其他用户的读、写、执行。

2 目录项结构(P602

  书上的图不完整。目录块应该指向一个i结点,i结点对应1个(或多个)目录项(为什么不叫文件项,因为目录下的不仅有文件还有子目录,统称为目录项)。这样看目录就是文件了。只不过目录块里的不是普通文件一样的内容,而是目录项。

3 设置用户ID位对于目录文件毫无用处,但是设置组ID对目录有特别的意义,如果该位被设置,则在该目录下创建的任何目录和文件都将设置为组ID

4 目录文件可以像普通文件一样有读写,执行,打开关闭,定位等操作,可以创建删除。但是内核没有提供目录的写函数。

5 创建目录删除目录

  mkdir(const char *pathname,mode_t mode)第一个参数是目录,第二个参数是访问权限。

  rmdir(const char *pathneme)

6 打开关闭读取目录

  打开目录也像打开文件一样返回一个描述符一样的东西(句柄DIR*),句柄是目录项结构体数组的指针;读取目录就是用这个句柄作参数,每读一次就定位到那里像读文件一样,可以循化返回所有目录项结构体(structdirent)。

* 计算目录下普通文件数目,file_countP605)。错误:too manyopen files

* my_ls没有成功(P607)

7 定位

  目录文件中第一个目录项是当前目录的目录项,名字为‘.’;第二个是父目录的目录项,名字是‘..’;然后是目录下的目录项。定位i函数:void seekdir(DIR * dir, off_toffset)第一个参数是目录句炳(描述符);第二个参数是定位值。回卷目录文件:可以使一个打开的目录文件读写位置回到目录文件的开始出,不必要重新关闭再打开了。回卷函数:rewinddir

8 进程工作目录

  用户登陆后,其默认的工作目录设置为用户的起始目录,该用户创建的进程的工作目录使用起始目录,如果该进程工作目录改变,其子进程的工作目录也就改变,其他进程不变;子进程工作目录改变,父进程不变,shell命令的程序都在/bin目录下,不是工作在/bin下,他们随shell程序来改变工作目录,shell为这些命令创建了子进程。shellcd命令改变。如果cd命令也像ls等命令一样就不能改变shell的工作目录(因为不能改变父进程工作目录),所以cd是在shell中实现的(调用了chdir),不是shell子进程。所以,一个程序要想更改工作目录只能调用chdir,不能调用系统命令cd。改变进程工作目录函数:int chdir(char *pathname)

  得到当前进程工作目录函数:char * getcwd(char *buf , size_t size)shellpwd就是调用了这个函数查看shell进程的工作目。

 

IO

IO是由C语言标准函数库提供的,是系统提供的readwrite的封装。可以减少文件读写的调用。

基于流IO提供以下3中缓冲:全缓冲、行缓冲、无缓冲。不同设备(文件)采用了不同的缓冲方式。设备已经抽象成了文件,在前面文件系统说到一个进程刚开始就会打开三个文件其实是一开始就会有三个stdio设备给进程使用,分别是标准输入(键盘),标准输出(显示屏),标准出错。

基于文件流的操作函数

打开:fopenfdopen关闭:fclose。打开返回一个文件流指针,就像文件描述符一样,可以对其使用流操作读写文件,fopen可以文件描述符上打开流。流文件关闭时候会自动对文件冲洗,把数据写入文件,网络环境中一定记得检查关闭的返回值。

   以字符为单位读写:fgetcfputc返回的是字符的值(不是字符)。

   以行为单位读写:

   char * fgetschar * restrict buf, int n ,FILE *restrictfp

   char * gets(char *buf)fputsputs gets标准输出,很不安全,缓冲区不能改变,容易发生崩溃。fgets中没遇到‘\n’,前n-1个缓冲区装人字符,最后一个装‘\0’;遇到‘\n’把‘\n’装入后装'\0'gets不装‘\n;fputsputs都不输出'\0'。缓冲区其实是装入n-1个字符的。

   -读写数据块

   size_t fread(void *restrict ptr,size_t size,size_t

   nobj.FILE * restrictfp)写:fwrite

   第一个参数是:输入输出缓冲区;第二个参数是:对象大小;第三个是输入输出对象个数。

   以上三种基于流的读写每次读写都是有定位的,和writeread一样

 

特殊文件

特殊的文件

1 符号链接

  win快捷方式一样,和前面的硬链接不一样。相当于提供了文件的一个读写通道,打开关闭操作和文件一样,也会返回一个fd,然后读写,其实就是对文件。符号链接作用1)方便操作;2)系统安全。可以链接不存在的文件,但是打不开。还可以创建多重符号链接(即在一个符号链接之上在创建一个符号链接)

  链接函数:int symlink(const char * acturalpath, const char* sympath)

  当然,符号连接也有自己的内容,要用自己的读写函数来读写:

  int readlink(constchar * restrict pathname,char *restrict buf ,size_t bufsize)内容是所链接文件的路径名链接符号的stat信息和目的文件的是一样的。lstat是符号链接本生的信息。chown不能改变符号链接的所有者,lchown可以改变。

特殊的文件系统

1 /proc

  是一个伪文件系统,只存在内存中,不占外存空间,可以使用Linux系统调用(例如openreadwrite等)访问进程地址空间和内核数据信息。有两种子目录,一是相关进程的ID,二是内核数据信息。

 

网 络

1 字节序

  本机上的字节序是小端序,网络字节序是大端序。转换字节序网络库提供了转换函数htonl(本机到网络),ntohl(网络到本机)。

2 几个地址结构体与地址转换网络地址二进制:in_addr;网络通信地址结构:socketadd_in(包含了in_addr);socketaddr(可与sockedd_in转换);主机信息hostent;由域名和服务名得到IP和端口信息getaddrinfo,返回一个socketadd_in转换IP地址格式:inet_ntop,inet_pton

3 套接字编程基础

  创建套接字:int socket(int domain,int type, int protocol)第一个参数是v4协议,第二个是UDP/TCP,第三个默认是0;成功就返回套接字描述符(就把他看成套接字)。地址绑定:intbind(int sockfd,const struct sockaddr *addr,socklen_t len)第一个是要绑定的套接字;第二个是绑定地址(通常需要强制转换);第三个是地址长度。

  建立链接:int connect(int sockfd,const struct sockadd *addr,socklen_t len)

  第一个是套接字,第二个是服务器地址connect_poll函数实现多次链接

监听函数:int listen(int sockfd,int backlog)第一个是要监听的套接字描述符,第二个是最多可以排队的请求数量。

  接受函数:int accept(int sockfd,struct sockaddr*addr,socklen len)返回一个新的套接字描述符,用这个可以读写远程客户端的数据。原来的套接字描述符继续监听。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值